Список работ

Бартеньев О. В.

Классификация изображений нейронной сетью

Содержание

Введение

Рассматривается задача классификация изображений, хранимых наборами данных MNIST [1], EMNIST-letters [2] и CIFAR-10 [3].
Решаются следующие задачи:

Модели НС реализуются на языке программирования Python средствами библиотеки Keras.
Обучаются разные модели НС с применением различных функций потерь, предоставляемых библиотекой Keras.
Качество обучения оценивается по трем следующим критериям:

  1. Точность классификации на тестовых данных.
  2. Точность классификации на обучающих данных.
  3. Суммарное число верно классифицированных изображений на тестовых и обучающих данных.

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

Описание наборов данных

Наборы данных содержат следующие изображения:

Замечание. Далее вместо EMNIST-letters будет употребляться EMNIST.

Примеры изображений наборов данных приведены на рис. 1-3.

MNIST

Рис. 1. MNIST: примеры изображений

EMNIST

Рис. 2. EMNIST: примеры изображений буквы A

CIFAR-10

Рис. 3. CIFAR-10: примеры изображений

При необходимости здесь можно загрузить файлы с данными:

Загрузка данных

Загрузка MNIST

В случае MNIST данные загружаются из следующих файлов:

Загрузку и вывод девяти цифр обучающего набора MNIST обеспечивает следующий код:

from mnist import MNIST
import numpy as np
import matplotlib.pyplot as plt
pathToData = 'G:\\AM\\НС\\mnist\\'
mndata = MNIST(pathToData) # pathToData – ранее заданный путь к папке с данными
mndata.gz = True # Разрешаем чтение архивированных данных
imagesTrain, labelsTrain = mndata.load_training()# Обучающая выборка (данные и метки)
imagesTest, labelsTest = mndata.load_testing()# Тестовая выборка (данные и метки)
X_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1)
# Выводим 9 изображений обучающего набора
names = []
for i in range(10): names.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
for i in range(9):
    plt.subplot(3, 3, i + 1)
    ind = y_train[i]
    img = X_train[i][0:28, 0:28, 0]
    plt.imshow(img, cmap = plt.get_cmap('gray'))
    plt.title(names[ind])
    plt.axis('off')
plt.subplots_adjust(hspace = 0.5)
plt.show()

Загрузка EMNIST

При работе с EMNIST использованы те же средства ввода данных, что и для MNIST. Для этого выполнены приведенные в табл. 1 изменения имен файлов EMNIST (тип файлов – gz Archive):

Таблица 1. Переименования имен файлов EMNIST

Исходное имя файлаИмя после переименованияРазмер архива (КБ)
EMNIST-train-images-idx3-ubytetrain-images-idx3-ubyte30'883
EMNIST-train-labels-idx1-ubytetrain-labels-idx1-ubyte78
EMNIST-test-images-idx3-ubytet10k-images-idx3-ubyte4'922
EMNIST-test-labels-idx1-ubytet10k-labels-idx1-ubyte1

Различия при вводе данных обусловлены тем, что в EMNIST буквы хранятся в горизонтальном положении и вдобавок отраженными относительно оси x (рис. 4).

EMNIST без преобразований

Рис. 4. Буквы EMNIST без преобразований отражения и поворота

В программе после чтения файла буквы отражаются относительно оси x и поворачиваются на 90° по часовой стрелке:

X_train = X_train.reshape(X_train.shape[0], img_rows, img_cols, 1).transpose(0,2,1,3)
X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1).transpose(0,2,1,3)

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

import numpy as np
from scipy import ndimage
...
let = X_test[0][0:28, 0:28, 0] # Первая буква тестового набора
let = let.reshape(32, 32)
let = np.flipud(let)
let = ndimage.rotate(let, -90)

или

let = let.reshape(32, 32).transpose()

Загрузка MNIST и EMNIST из бинарных файлов

Ввод данных будет выполнен быстрее, если их загружать из бинарных файлов.
Архивы таких файлов имеются для MNIST и EMNIST.

Код создания MNIST-бинарных файлов:

from mnist import MNIST
import numpy as np
# Прежде загружаем данные
mndata = MNIST(pathToData)
mndata.gz = True
imagesTrain, labelsTrain = mndata.load_training()
imagesTest, labelsTest = mndata.load_testing()
# Сохраняем данные в двоичные файлы
fn = open(pathToData + 'imagesTrain.bin', 'wb')
fn2 = open(pathToData + 'labelsTrain.bin', 'wb')
fn3 = open(pathToData + 'imagesTest.bin', 'wb')
fn4 = open(pathToData + 'labelsTest.bin', 'wb')
fn.write(np.uint8(imagesTrain))
fn2.write(np.uint8(labelsTrain))
fn3.write(np.uint8(imagesTest))
fn4.write(np.uint8(labelsTest))
fn.close()
fn2.close()
fn3.close()
fn4.close()
print('MNIST-данные сохранены в двоичные файлы')

Код чтения бинарных файлов (предварительно выполняется распаковка ранее загруженного архива с бинарными файлами):

def loadBinData(pathToData):
    import numpy as np
    print('Загрузка данных из двоичных файлов')
    with open(pathToData + 'imagesTrain.bin', 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTrain.bin', 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    return data, labels, data2, labels2
#
pathToData = 'G:\\AM\\НС\\mnist\\'
imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData(pathToData)
X_train = np.asarray(imagesTrain)
y_train = np.asarray(labelsTrain)
X_test = np.asarray(imagesTest)
y_test = np.asarray(labelsTest)
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)

Загрузка CIFAR-10

CIFAR-10 загружается из файлов, расположенных в папке

pathToData + cifar-10-batches-py/

В этой папке файлы

data_batch_1, data_batch_2, data_batch_3, data_batch_4, data_batch_5

содержат обучающие данные, а файл

test_batch

содержит тестовые данные.

Размер каждого файла 30'309 КБ. Каждый файл содержит по 10'000 изображений и их метки.

Код загрузки CIFAR-10 и вывода 50-и случайных изображений обучающего набора:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
#
def load_cifar10(pathToData): # pathToData – путь к папке с данными
    def unpickle(file):
        import pickle
        with open(file, 'rb') as fo:
            dict_file = pickle.load(fo, encoding = 'bytes')
        return dict_file
    fileNm = pathToData + 'cifar-10-batches-py/data_batch_'
    # Загрузка обучающих данных
    im_size = 32 * 32 * 3 # 3072
    X_train = np.zeros((50000, im_size), dtype = 'uint8')
    y_train = np.zeros(50000, dtype = 'uint8')
    m = -1
    for i in range(5):
        dict_i = unpickle(fileNm + str(i + 1))
        X = dict_i[b'data']
        Y = dict_i[b'labels']
        Y = np.array(Y)
        for k in range(10000):
            m += 1
            X_train[m, :] = X[k, :]
            y_train[m] = Y[k]
    X_train = X_train.reshape(-1, 3, 32, 32).transpose(0,2,3,1)
    # Загрузка тестовых данных
    len_test = 10000
    fileNm = pathToData + 'cifar-10-batches-py/test_batch'
    dict_i = unpickle(fileNm)
    X_test = dict_i[b'data']
    y_test = dict_i[b'labels']
    X_test = X_test.reshape(len_test, 3, 32, 32).transpose(0,2,3,1) # .astype('uint8')
    y_test = np.array(y_test)
    # Выводим 50 случайных изображений обучающего набора
    names = []
    names.append('Самолет')
    names.append('Авто')
    names.append('Птица')
    names.append('Кошка')
    names.append('Олень')
    names.append('Собака')
    names.append('Лягушка')
    names.append('Лошадь')
    names.append('Судно')
    names.append('Грузовик')
    fig, axs = plt.subplots(5, 10, figsize = (7, 4))
    title_font = {'fontname':'Arial', 'size':'9', 'color':'black'}
    for j in range(5):
        for k in range(10):
            i = np.random.choice(range(50000))
            ax1 = axs[j][k]
            ax1.set_axis_off()
            ax1.imshow(X_train[i])
            ax1.set_title(names[y_train[i]], **title_font)
    plt.subplots_adjust(hspace = 0.5, wspace = 0.0)
    plt.show()
pathToData = 'G:\\AM\\НС\\cifar10\\'
load_cifar10(pathToData)

Функции потерь

Обучение моделей НС выполняется с различными функциями потерь библиотеке Keras [4] на наборах данных MNIST, EMNIST и CIFAR-10.
Используемые функции потерь и их обозначения перечислены в табл. 2.

Таблица 2. Функции потерь библиотеки Keras

ФункцияОбозначение
1Средняя квадратическая ошибка / mean squared errormse
2Средняя абсолютная ошибка / mean absolute errormae
3Средняя абсолютная процентная ошибка / mean absolute percentage errormape
4Средняя квадратическая логарифмическая ошибка / mean squared logarithmic errormsle
5Квадрат верхней границы / squared hingesh
6Верхняя граница / hingeh
7Категориальная верхняя граница / categorical hingech
8Логарифм гиперболического косинуса / logcoshlc
9Категориальная перекрестная энтропия / categorical crossentropycce
10Разреженная категориальная перекрестная энтропия / sparse categorical crossentropyscce
11Бинарная перекрестная энтропия / binary crossentropybce
12Расстояние Кульбака-Лейблера / kullback leibler divergencekld
13Пуассон / Poissonpss
14Косинусная близость / cosine proximitycp

Кроме перечисленных в табл. 2. функций потерь, применяются:

Описание моделей нейронных сетей

Классификация изображений MNIST, EMNIST и CIFAR-10 выполняется с использованием сверточных НС (СНС), созданных на основе структуры, приведенной на рис. 5.

СНС для MNIST, EMNIST и CIFAR-10

Рис. 5. Структура базовой СНС для MNIST, EMNIST и CIFAR-10

Построение СНС выполнялось в результате добавления слоев (сверточных, полносвязных, разреживающих и пакетной нормализации), также использовались и сборки СНС с двумя и тремя ветвями.
Поскольку СНС, построенные на базе приведенной на рис. 5 структуры, показали низкие результаты обучения на CIFAR-10, вдобавок была использована сеть ReNet20v1, показывающая, согласно [5], на CIFAR-10 точность классификации 91.25% на проверочных данных.
ReNet20v1 состоит секций, содержащих показанные на рис. 6 блоки.

Блоки ResNet

Рис. 6. Блоки секций ResNet20v1

Всего в ReNet20v1 три секции. Первая секция состоит из трех а-блоков, вторая и третья – из б-блока и двух а-блоков (рис. 7).

Секции ResNet

Рис. 7. Структуры секций ResNet20v1

Полностью структура ReNet20v1 показана на рис. 8.

Структура ResNet

Рис. 8. Структура ResNet20v1

Кроме того, для MNIST использовались модели многослойного перцептрона (МП) и рекуррентной нейронной сети (РНС) с LSTM-слоем (рис. 9).

MLP and LSTM

Рис. 9. Структуры НС: а – МП; б – РНС

Для вывода рис. 5 и 9 использована процедура plot_model:

import keras
from keras.utils import plot_model
model = createModel() # Формирование модели СНС
plot_model(model, to_file = 'cnn_graph.png') # Вывод графа модели СНС

Замечание. Для plot_model необходимо установить graphviz (нужна библиотека gvc.dll).

В MNIST и EMNIST изображения заданы в оттенках серого цвета на площадке размером 28*28 пикселей, что определяет следующую форму входа:

input_shape = (28, 28, 1)

В CIFAR-10 изображения цветные и имеют размер 32*32 пикселя, поэтому задаем следующую форму входа:

input_shape = (32, 32, 3)

Форма выходного слоя определяется числом классов. В MNIST и CIFAR-10 их 10, а в EMNIST – 26.

В табл. 3 приведены описания слоев одной моделей НС, использованной для MNIST, EMNIST и CIFAR-10.

Таблица 3. Описание слоев СНС c4 для MNIST, EMNIST и CIFAR-10

Layer (type)Output ShapeParam #
input_1 (InputLayer)MNIST, EMNIST: (None, 28, 28, 1)
CIFAR-10: (None, 32, 32, 3)
0
conv2d_1 (Conv2D)MNIST, EMNIST: (None, 28, 28, 20)
CIFAR-10: (None, 32, 32, 20)
340
560
max_pooling2d_1 (MaxPooling2D)MNIST, EMNIST: (None, 14, 14, 20)
CIFAR-10: (None, 16, 16, 20)
0
conv2d_2 (Conv2D)MNIST, EMNIST: (None, 14, 14, 30)
CIFAR-10: (None, 16, 16, 30)
9'630
5'430
max_pooling2d_2 (MaxPooling2D)MNIST, EMNIST: (None, 7, 7, 30)
CIFAR-10: (None, 8, 8, 30)
0
flatten_1 (Flatten)MNIST, EMNIST: (None, 1470)
CIFAR-10: (None, 1920)
0
dense_1 (Dense)(None, 600)MNIST, EMNIST: 882'600
CIFAR-10: 1'152'600
dense_2 (Dense)MNIST: (None, 30)MNIST, EMNIST: 18'030
CIFAR-10: 18'030
dense_3 (Dense)MNIST, CIFAR-10: (None, 10)
EMNIST: (None, 26)
310
806
Total params (всего параметров):
MNIST: 910'910
EMNIST: 911'406
CIFAR-10: 1'176'930
Trainable params (обучаемых параметров):
MNIST: 910'910
EMNIST: 911'406
CIFAR-10: 1'176'930
Non-trainable params (необучаемых параметров): 0

Описание слоев ResNet20v1 приведено в табл. 4.

Таблица 4. Описание слоев ResNet20v1 для CIFAR-10

Layer (type)Output ShapeParam #Connected to
input_1 (InputLayer)(None, 32, 32, 3)0
conv2d_1 (Conv2D)(None, 32, 32, 16)448input_1[0][0]
batch_normalization_1 (BatchNormalization)(None, 32, 32, 16)64conv2d_1[0][0]
activation_1 (Activation)(None, 32, 32, 16)0batch_normalization_1[0][0]
conv2d_2 (Conv2D)(None, 32, 32, 16)2'320activation_1[0][0]
batch_normalization_2 (BatchNormalization)(None, 32, 32, 16)64conv2d_2[0][0]
activation_2 (Activation)(None, 32, 32, 16)0batch_normalization_2[0][0]
conv2d_3 (Conv2D)(None, 32, 32, 16)2'320activation_2[0][0]
batch_normalization_3 (BatchNormalization)(None, 32, 32, 16)64conv2d_3[0][0]
add_1 (Add)(None, 32, 32, 16)0activation_1[0][0]
batch_normalization_3[0][0]
activation_3 (Activation)(None, 32, 32, 16)0add_1[0][0]
conv2d_4 (Conv2D)(None, 32, 32, 16)2'320activation_3[0][0]
batch_normalization_4 (BatchNormalization)(None, 32, 32, 16)64conv2d_4[0][0]
activation_4 (Activation)(None, 32, 32, 16)0batch_normalization_4[0][0]
conv2d_5 (Conv2D)(None, 32, 32, 16)2'320activation_4[0][0]
batch_normalization_5 (BatchNormalization)(None, 32, 32, 16)64conv2d_5[0][0]
add_2 (Add)(None, 32, 32, 16)0activation_3[0][0]
batch_normalization_5[0][0]
activation_5 (Activation)(None, 32, 32, 16)0add_2[0][0]
conv2d_6 (Conv2D)(None, 32, 32, 16)2'320activation_5[0][0]
batch_normalization_6 (BatchNormalization)(None, 32, 32, 16)64conv2d_6[0][0]
activation_6 (Activation)(None, 32, 32, 16)0batch_normalization_6[0][0]
conv2d_7 (Conv2D)(None, 32, 32, 16)2'320activation_6[0][0]
batch_normalization_7 (BatchNormalization)(None, 32, 32, 16)64conv2d_7[0][0]
add_3 (Add)(None, 32, 32, 16)0activation_5[0][0]
batch_normalization_7[0][0]
activation_7 (Activation)(None, 32, 32, 16)0add_3[0][0]
conv2d_8 (Conv2D)(None, 16, 16, 32)4'640activation_7[0][0]
batch_normalization_8 (BatchNormalization)(None, 16, 16, 32)128conv2d_8[0][0]
activation_8 (Activation)(None, 16, 16, 32)0batch_normalization_8[0][0]
conv2d_9 (Conv2D)(None, 16, 16, 32)9'248activation_8[0][0]
conv2d_10 (Conv2D)(None, 16, 16, 32)544activation_7[0][0]
batch_normalization_9 (BatchNormalization)(None, 16, 16, 32)128conv2d_9[0][0]
add_4 (Add)(None, 16, 16, 32)0conv2d_10[0][0]
batch_normalization_9[0][0]
activation_9 (Activation)(None, 16, 16, 32)0add_4[0][0]
conv2d_11 (Conv2D)(None, 16, 16, 32)9'248activation_9[0][0]
batch_normalization_10 (BatchNormalization)(None, 16, 16, 32)128conv2d_11[0][0]
activation_10 (Activation)(None, 16, 16, 32)0batch_normalization_10[0][0]
conv2d_12 (Conv2D)(None, 16, 16, 32)9'248activation_10[0][0]
batch_normalization_11 (BatchNormalization)(None, 16, 16, 32)128conv2d_12[0][0]
add_5 (Add)(None, 16, 16, 32)0activation_9[0][0]
batch_normalization_11[0][0]
activation_11 (Activation)(None, 16, 16, 32)0add_5[0][0]
conv2d_13 (Conv2D)(None, 16, 16, 32)9'248activation_11[0][0]
batch_normalization_12 (BatchNormalization)(None, 16, 16, 32)128conv2d_13[0][0]
activation_12 (Activation)(None, 16, 16, 32)0batch_normalization_12[0][0]
conv2d_14 (Conv2D)(None, 16, 16, 32)9'248activation_12[0][0]
batch_normalization_13 (BatchNormalization)(None, 16, 16, 32)128conv2d_14[0][0]
add_6 (Add)(None, 16, 16, 32)0activation_11[0][0]
batch_normalization_13[0][0]
activation_13 (Activation)(None, 16, 16, 32)0add_6[0][0]
conv2d_15 (Conv2D)(None, 8, 8, 64)18'496activation_13[0][0]
batch_normalization_14 (BatchNormalization)(None, 8, 8, 64)256conv2d_15[0][0]
activation_14 (Activation)(None, 8, 8, 64)0batch_normalization_14[0][0]
conv2d_16 (Conv2D)(None, 8, 8, 64)36'928activation_14[0][0]
conv2d_17 (Conv2D)(None, 8, 8, 64)2'112activation_13[0][0]
batch_normalization_15 (BatchNormalization)(None, 8, 8, 64)256conv2d_16[0][0]
add_7 (Add)(None, 8, 8, 64)0conv2d_17[0][0]
batch_normalization_15[0][0]
activation_15 (Activation)(None, 8, 8, 64)0add_7[0][0]
conv2d_18 (Conv2D)(None, 8, 8, 64)36'928activation_15[0][0]
batch_normalization_16 (BatchNormalization)(None, 8, 8, 64)256conv2d_18[0][0]
activation_16 (Activation)(None, 8, 8, 64)0batch_normalization_16[0][0]
conv2d_19 (Conv2D)(None, 8, 8, 64)36'928activation_16[0][0]
batch_normalization_17 (BatchNormalization)(None, 8, 8, 64)256conv2d_19[0][0]
add_8 (Add)(None, 8, 8, 64)0activation_15[0][0]
batch_normalization_17[0][0]
activation_17 (Activation)(None, 8, 8, 64)0add_8[0][0]
conv2d_20 (Conv2D)(None, 8, 8, 64)36'928activation_17[0][0]
batch_normalization_18 (BatchNormalization)(None, 8, 8, 64)256conv2d_20[0][0]
activation_18 (Activation)(None, 8, 8, 64)0batch_normalization_18[0][0]
conv2d_21 (Conv2D)(None, 8, 8, 64)36'928activation_18[0][0]
batch_normalization_19 (BatchNormalization)(None, 8, 8, 64)256conv2d_21[0][0]
add_9 (Add)(None, 8, 8, 64)0activation_17[0][0]
batch_normalization_19[0][0]
activation_19 (Activation)(None, 8, 8, 64)0add_9[0][0]
average_pooling2d_1 (AveragePoo(None, 1, 1, 64)0activation_19[0][0]
flatten_1 (Flatten)(None, 64)0average_pooling2d_1[0][0]
dense_1 (Dense)(None, 10)650flatten_1[0][0]
Total params: 274'442
Trainable params: 273'066
Non-trainable params: 1'376

Сведения, приведенные в табл. 3 и 4, получены в результате выполнения следующего кода:

model = createModel() # Формирование модели НС
model.summary() # Вывод сведений о слоях модели НС

Введем следующее полное обозначение слоя описанной в тпбл. 3 модели НС:

<Тип слоя>[<Вид функции активации>][<форма входа | число фильтров | нейронов>],

где <Тип слоя> – это символ I, C, D, P или F соответственно для входного, сверточного, полносвязного слоев или слоев подвыборки и преобразования многомерных данных в одномерные;
<Вид функции активации> – это символ R, L или S соответственно для функции активации ReLU, Linear или Softmax.
Для слоев типа P и F указанные в квадратных скобках компоненты обозначения отсутствуют.
Знак "|" использован для обозначения "или".
Запишем приведенные выше структуры СНС (кроме ResNet20v1) в виде следующих строк:

I(28,28,1)-CR20-P-CR30-P-F-DR600-DL30-DS10 # MNIST;
I(28,28,1)-CR20-P-CR30-P-F-DR600-DL30-DS26 # EMNIST;
I(32,32,3)-CR20-P-CR30-P-F-DR600-DL30-DS10 # CIFAR-10,

В дальнейшем, записывая таким образом структуру НС, будем опускать входной и выходной слои, например:

CR20-P-CR30-P-F-DR600-DL30.

Указание этой записи для MNIST означает, что на входе слой I(28,28,1), а на выходе DS10.
В случае EMNIST на входе I(28,28,1), а на выходе DS26.
В случае CIFAR-10 на входе I(32,32,3), а на выходе DS10.
Dropout-слой обозначается указанием параметра rate, задающего долю нейронов, исключаемых из модели НС, например, запись

CR20-P-CR30-P-F-0.25-DR600-DL16

означает, что перед полносвязным слоем

Dense(units = 600, activation = 'relu'...)

расположен слой

Dropout(rate = 0.25)

Присутствие слоя пакетной нормализации будем обозначать символами BN.

Обозначение LSTM-слоя рекуррентной сети (для слоя берется заданная по умолчанию функция активации):

Ln,

где n – размер выхода слоя.

Используя введенные обозначения, перечислим в табл. 5 модели НС, примененные для классификации изображений MNIST, EMNIST и CIFAR-10.

Таблица 5. Примененные модели НС

Вид НСОбозначениеЧисло параметровИспользование
c01Сверточная CR32-P-CR64-P-F-DR1024-DL303'276'724
3'277'220
4'245'780
MNIST
EMNIST
CIFAR-10
c1тот жеCR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 то жето же
c02тот же CR32-P-CR64-P-F-DR1024-DL163'262'234
3'262'506
MNIST
EMNIST
c2тот же CR32-P-CR64-P-F-0.35-DR1024-0.2-DL16то жето же
c03тот же CR32-P-CR64-P-F-DR1024-DL523'299'494
3'300'342
MNIST
EMNIST
c3тот же CR32-P-CR64-P-F-0.35-DR1024-0.2-DL52то жето же
c4тот жеCR20-P-CR30-P-F-DR600-DL30910'910
911'406
1'176'930
MNIST
EMNIST
CIFAR-10
c5тот жеCR20-P-CR30-P-F-0.2-DR100158'080
159'696
199'100
MNIST
EMNIST
CIFAR-10
c6тот жеCR20-P-CR30-P-F-DR600898'580MNIST
c7тот жеCR10-P-CR15-P-F-0.2-DR300226'395
231'211
292'955
MNIST
EMNIST
CIFAR-10
c8тот жеCR10-P-CR15-P-F-0.25-DR300-DL30232'725MNIST
c9тот жеCR20-P-CR30-P-F-DR600-DL16902'356
902'628
MNIST
EMNIST
c10тот жеCR20-P-CR30-P-F-DR600-DL52924'352
925'200
MNIST
EMNIST
c11тот жеCR20-P-CR30-P-F-0.25-DR600-DL16902'356
902'628
MNIST
EMNIST
c12тот жеCR20-P-CR30-P-F-0.25-DR600-0.2-DL16то жето же
c12bnтот жеC20-BN-R-P-C30-BN-R-P-F-0.25-D600-BN-R-0.2-D16-BN-L903'688 MNIST
c13тот жеCR20-P-CR30-P-F-0.25-DR600-DL30910'910
911'406
1'176'930
MNIST
EMNIST
CIFAR-10
c14тот жеCR20-P-CR30-P-F-0.25-DR600-0.2-DL30то жето же
c15тот жеCR32-P-CR64-P-F-0.25-DR800-DL162'555'962
2'556'234
3'309'978
MNIST
EMNIST
CIFAR-10
c16тот жеCR32-P-CR64-P-F-0.25-DR800-0.2-DL16то жето же
c17тот жеCR32-P-CR64-P-F-0.25-DR800-DL302'567'316
2'567'812
3'321'332
MNIST
EMNIST
CIFAR-10
c18тот жеCR32-P-CR64-P-F-0.25-DR800-0.25-DL30то жето же
c19тот жеCR10-P-CR15-P-F-0.25-DR300226'395
231'211
292'955
MNIST
EMNIST
CIFAR-10
c20тот жеCR10-P-CR15-P-F-0.3-DR300то жето же
c21тот жеCR10-P-CR15-P-F-0.35-DR300то жето же
c22тот жеCR10-P-CR15-P-F-0.35-DR300-0.3то жето же
c23тот жеCR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2947'570
949'026
1'213'590
MNIST
EMNIST
CIFAR-10
c24тот жеCR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2то жето же
c25тот жеCR20-P-CR30-P-F-DR600-DL60929'240
930'216
1'195'260
MNIST
EMNIST
CIFAR-10
c26тот жеCR20-P-CR30-P-F-0.3-DR600-DL60то жето же
c27тот жеCR20-P-CR30-P-F-0.3-DR600-0.2-DL60то жето же
c28тот жеCR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2то жето же
c28bnтот жеC20-BN-R-P-C30-BN-R-P-F-0.3-D600-BN-R-0.2-D60-BN-L-0.2931'636 EMNIST
c29тот жеCR10-P-CR15-P-F-0.3-DR300-0.25-DL50-0.2238'945
239'761
305'505
MNIST
EMNIST
CIFAR-10
c30тот жеCR20-P-0.25-CR30-CR30-P-0.25-F-DR512-0.5772'042
780'250
1'002'802
MNIST
EMNIST
CIFAR-10
c31тот жеCR15-P-CR10-P-F-0.4-DR300152'975
157'971
197'090
MNIST
EMNIST
CIFAR-10
c32тот жеCR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.35-DR128-0.25899'306
901'370
1'132'698
MNIST
EMNIST
CIFAR-10
c32тот жеCR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.35-DR128-0.25899'306
901'370
1'132'698
MNIST
EMNIST
CIFAR-10
c33тот жеCR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.3-DL32-0.2849'098
849'626
1'082'490
MNIST
EMNIST
CIFAR-10
c34тот жеC32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.51'728'074
1'736'282
2'169'770
MNIST
EMNIST
CIFAR-10
c35тот жеBN-CR32-CR32-P-0.25-BN-CR64-CR64-P-0.25-F-DR512-0.5-DR128-0.251'788'556
1'790'620
2'230'256
MNIST
EMNIST
CIFAR-10
c36тот жеResNet20v1272'778
273'818
273'066
MNIST
EMNIST
CIFAR-10
c37тот жеResNet32v1467'658
470'970
467'946
MNIST
EMNIST
CIFAR-10
m1МПDR800-DR80-DL30694'820MNIST
m2тот жеDR800-DR80-DL16693'546MNIST
m3тот жеDR800-DR80-DL52696'822MNIST
r1РНСL300-DL30404'140MNIST
r2тот жеL500-DL301'073'340MNIST

В некоторых случаях используется сборка НС, построенная на основе одной из описанных в табл. 5 моделей НС (пример сборки показан на рис. 10).

Сборка НС

Рис. 10. Сборочная НС (2c8) с двумя ветвями на основе сети c8

В этом случае перед в обозначении сборочной сети указывается число ветвей и обозначение базовой структуры, например, 2c8.
Для объединения идущих по ветвям сборки данных используется слой Average. Иные способы объединения данных описаны в [6].
Модель с конкретной функцией потерь обозначается следующим образом:

<имя модели> (<имя функции потерь>), например, 2c8(kld). При повторном обучении модели в ее имени присутствует номер обучения, например:

Результаты обучения перечисленных в табл. 5 моделей НС с применением разных функций потерь, приведены в прил. "Результаты обучения нейронных сетей".

Параметры моделей нейронных сетей

В сетях различной структуры использованы следующие параметры:

Замечание. Применение ранней остановки с параметрами monitor = 'loss' и patience = 9 означает, что обучение будет прервано, если в течение 9-и подряд идущих эпох не снижались потери на обучающей выборке.

Классификация изображений выполняется с применением перечисленных выше функций потерь.

Сохранение и загрузка весов

В процессе обучения веса (параметры) модели НС будут записаны в файл после завершения эпохи, если результаты оценки НС по точности классификации на проверочных данных (monitor = 'val_acc') после этой эпохи превосходят ранее полученные результаты. Это обеспечивает следующий код:

# Обеспечим сохранение обученной сети
suf = 'MNIST_mape_Adam_conv_' # Значение suf может быть иным
filesToSave = 'weights_' + suff + '.{epoch:02d}-{val_acc:.2f}.hdf5'
say = 'веса' if weightsOnly else 'модель и веса'
print('Сохраняем веса в файлы вида ' + filesToSave)
pathWeights = pathToData + filesToSave
# Сохраняем и веса, и модель: save_weights_only = False
checkpoint = keras.callbacks.ModelCheckpoint(pathWeights, monitor = 'val_acc', verbose = 0,
                                             save_weights_only = True,
                                             save_best_only = True, mode = 'max', period = 1)
callbacks_list = [checkpoint]
# Обучение
# Запоминаем историю обучения для последующего анализа и вывода графиков потерь и точности
history = model.fit(X_train, y_train, batch_size = batch_size, epochs = epochs,
                    verbose = 2, validation_data = (X_test, y_test), callbacks = callbacks_list)

Пример имени файла с весами: weights_MNIST_mape_Adam_c12.36-0.99.hdf5

В имени файла указаны имя набора данных, применяемые функция потерь (mape) и метод оптимизации (Adam), обозначение НС (c12), номер эпохи, после завершения которой сохранены веса (36), и точность классификации на проверочных данных с двумя знаками после запятой.
Сохраненные веса модели НС (назовем ее исходной) можно загрузить в программу, предварительно создав модель НС, совпадающую с исходной. Кроме того, в создаваемую для загрузки весов модель НС можно добавить слои Dropout, либо исключить эти слои при их наличии в исходной модели или изменить некоторые параметры модели НС, например, функции активации, не влияющие на число весов в модели.
Модель с закруженными весами можно употребить для следующих целей:

По полученному массиву y_pred можно, в частности, получить индексы неверно классифицированных изображений и вывести ошибочный прогноз и истинное имя классса или его метку:

# X - тестируемые данные
# y - метки тестируемых данных
# n - число тестируемых данных
imgType = 'MNIST' # или 'EMNIST' или 'CIFAR10'
y_pred = model.predict(X) # Предсказания модели НС на обучающей или тестовой выборке
# Заносим в список classes метки классов, предсказанных моделью НС
classes = []
for m in y_pred: classes.append(np.argmax(m))
# Число верно классифицированных изображений
nClassified = np.sum(classes == y)
# Число ошибочно классифицированных изображений
nNotClassified = n - nClassified
acc = 100.0 * nClassified / n
print("Число ошибочно классифицированных образов: " + str(nNotClassified))
print("Точность прогнозирования: " + str(acc) + '%')
print('Список неверно классифицированных изображений')
names = [] # Список с именами классов
if imgType == 'MNIST':
    for i in range(10): names.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
elif imgType == 'EMNIST':
    for i in range(26): names.append(chr(65 + i)) # ['A', 'B', 'C', ..., 'Z']
elif imgType == 'CIFAR10':
    names.append('Самолет')
    names.append('Авто')
    names.append('Птица')
    names.append('Кошка')
    names.append('Олень')
    names.append('Собака')
    names.append('Лягушка')
    names.append('Лошадь')
    names.append('Судно')
    names.append('Грузовик')
# m - число ошибочных предсказаний
m = 0
for i in range(n):
    s_true = names[y[i]] # Истинное имя класса
    s_pred = names[classes[i]] # Предсказанное моделью имя класса
    if s_true != s_pred:
        m += 1
        if (m > nNotClassified): break
        print(str(m) + '. i = ' + str(i) + '. На самом деле: ' + s_true + '. Прогноз: ' + s_pred)

Оценку обученной модели НС можно выполнить, применив метод evaluate:

# Оценка модели НС на тестовых данных
score = model.evaluate(X_test, y_test, verbose = 0)
# Вывод потерь и точности
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])

Оценка результатов обучения

Введем следующие понятия:

Примеры имен файлов с историей обучения:

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

Несколько строк из истории обучения c1(kld) приведены в табл. 6.

Таблица 6. Фрагмент истории обучения c1(kld)

Эпохаacclossval_accval_loss
460.999350.0022948933054906470.99440.026391212741745676
470.99921666666666660.0028415887921660520.99370.03381528404881883
480.99916666663487750.00344500704874808450.99370.03047861402691924
490.99871666666666670.0043079203832312490.9930.03465996994199886
500.99901666666666670.0033808891978619310.99410.02796838024803625
510.99930.00278010582679077450.99410.028028475116779646
520.9990.00338441957572552680.9940.029609011523109803
530.99876666666666670.00384716914516563220.99370.03257301945153831
540.99878333333333340.00415747884088068540.99320.033359301897392495
550.9990.00313648995518063520.99320.03427551583018867

Показатели acc, loss, val_acc и val_loss можно использовать для оценки качества обучения модели НС и выбора решения.
Введем еще один показатель:

corr_img – суммарное число правильно классифицированных изображений на обучающей и тестовых выборках.

Замечание. При выводе результатов для наглядности вместо corr_img используется значение err_img – суммарное число ошибочно классифицированных изображений на обучающих и проверочных данных:

err_img = all_img – corr_img,

где all_img – число изображений в наборе данных.

В дальнейшем показатели acc, loss, val_acc, val_loss и corr_img (err_img) будем называть критериями обучения.

В некоторых случая в истории обучения имеется эпоха, после которой acc и val_acc имеют (по сравнению с другими эпохами) максимальное значение, а loss, val_loss и err_img – минимальное.
Пример: модель c1(msle) после 44-й эпохи на MNIST:

acc = 99.845%; loss = 0.000133; val_acc = 99.52%; val_loss = 0.000416; err_img = 142.

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

Таблица 7. Лучшие эпохи (на MNIST) модели c17(sссe)

Эпохаacc, %lossval_acc, %val_losserr_img
699.42799.310.0179190.024047413
4699.95899.340.0012290.03335591
5399.96599.390.0013190.03315982
5499.93399.510.0021350.03240489

Замечание. Лучшие значения критериев обучения модели c17(sссe) в приведенной выше табл. 7 указаны полужирным шрифтом.

Таким образом, если лучшие значения критериев получены в разных эпохах, возникает задача выбора лучшего решения.
Нередко результаты обучения оцениваются по val_acc или по ошибке, равной 100 – val_acc (см., например, [7, 8]).
Такая оценка не дает полного представления о качестве обучения. Например, на MNIST максимум val_acc моделей c5(sh), c5(cce), c5(kld) и c5(pss) одинаков (val_acc = 99.4), однако значения acc и, следовательно, err_img различны (табл. 8):

Таблица 8. Сравнение решений с одинаковым val_acc

Модельval_acc, %acc, % err_img
c5(sh)99.499.876667114
c5(cce)99.499.673333236
c5(kld)99.499.89333104
c5(pss)99.499.95567

Учитывая эту информацию, лучшим следует признать решение c5(pss).

Замечание 1. При обучении минимизируется значение функции потерь (loss) на обучающих данных. Однако этот показатель, как следует из [7, 8], редко применяется при выборе решения.

В табл. 9 показано, насколько серьезно решения, выбранные по критериям acc, val_acc и err_img, отличаются по потерям от решения с наименьшими потерями (берутся потери на обучающей выборке).

Таблица 9. Среднее относительное отклонение по потерям между лучшими решениями и решениями с наименьшими потерями

КритерийОтклонение, %
Лучшие решения всего СОЛучшие решения каждой модели СО
MNISTEMNISTCIFAR-10MNISTEMNISTCIFAR-10
acc1.240.420.02.950.360.2
val_acc31.6618.282.8238.1857.274.1
err_img0.151.220.07.540.770.36

Заполнение табл. 9 выполнено по следующему алгоритму:

Входные данные. Имя набора данных, критерий (acc, val_acc или err_img), список и истории обучения НС выбранного набора данных.
Выходные данные. loss_dif – среднее относительное отклонение (в %) по потерям между лучшими решениями и решениями с наименьшими потерями.

  1. Сформировать список лучших решений (СЛР) по выбранному критерию.
  2. loss_dif = 0 # Искомое среднее относительное отклонения по потерям
  3. Для каждого решения из СЛР цикл:
        Найти потери loss_crit в эпохе лучшего решения.
        Найти эпоху с наименьшими потерями loss и потери loss_loss в этой эпохе.
        loss_dif = loss_dif + (loss_crit – loss_loss) / loss_loss
    конецЦикла
  4. loss_dif = 100 * loss_dif / размер_СЛР

Погрешность измерения значения критерия

Измерение значения критерия – это обучение НС и последующая оценка обученной модели с целью вычисления значений критериев, которые и являются результатами измерений.
При работе с Keras при повторном обучении модели НС с неизмененными значениями параметров новое решение, если не принять специальных мер, как правило, отличается от прежнего. Примеры таких расхождений представлены в табл. 10, также такие примеры имеются и в прил. 1

Таблица 10. Результаты повторных обучений моделей НС

Функция потерьНомер обученияОписание решения
Эпохаval_accΔval_accaccΔaccerr_imgΔerr_img
MNIST: модель 2c12
mse14399.600.0299.860.0051241
24299.5699.87122
37899.57--
mae15399.500.02599.670.01523816.5
24599.4599.64271
36499.47--
mape15199.530.00599.630.0052693
24799.5399.64263
33599.52--
msle17499.570.02599.900.0210315
24699.5799.85133
37799.52--
sh14299.510.0199.8501391
24599.5399.85137
39999.51--
h15599.500.00599.670.012486
24999.5099.65260
38899.51--
ch14099.450.0199.590.0330117.5
24699.4799.65266
36299.46--
lc15199.550.02599.870.011233.5
26499.5399.89116
311799.58--
cce13199.550.00599.80.04516526.5
25699.5499.89112
311799.55--
scce13399.570.0199.760.0051840.5
23499.5599.77183
35899.56--
bce15299.570.02599.860.01512713.5
25299.5299.83154
311399.55--
kld14599.530.01599.840.0214313.5
24199.5699.88116
33699.56--
pss15399.520.0599.850.0213815.5
23999.4599.81169
36899.55--
cp14199.510.02599.850.03513923.5
26299.5699.9292
310799.52--
ck16499.530.02599.90.02510710.5
24299.5899.84138
39899.56--
mk13799.530.0499.820.0215511
24399.5199.86133
36899.59--
EMNIST: модель 2c28
mse13394.860.0396.540.0815387404.5
25394.9197.184578
33194.92--
mae15491.051.34590.741.38134181877
25293.7493.39664
35293.56--
mape13292.910.0792.790.3410336357.5
25092.7792.3511051
34592.89--
msle14694.910.02597.140.06462874
25394.9097.264480
34494.95--
sh16494.860.2296.710.1754052783
25394.8396.365618
34094.42--
h17092.690.9392.160.84113041183
22891.4090.4813670
32593.36--
ch14094.140.0893.990.045871961
23294.1994.088597
32794.03--
lc19494.940.04597.410.08428590.5
26195.0397.254466
34294.97--
cce17494.760.0897.170.045462244.5
26494.8797.084711
38394.92--
scce16394.840.05597.150.2054630252.5
210794.8197.564125
37694.92--
bce15394.800.04597.70.2753952336
29094.8797.154624
35494.89--
kld15994.810.0496.930.04491152
25994.8397.014807
35294.89--
pss16994.920.0297.180.184582224.5
210494.8997.544133
39894.93--
cp16494.820.0697.460.1054247137
27494.8897.673973
38394.94--
ck18794.870.0197.320.194412238
26494.8696.944888
310594.88--
mk16494.850.0197.340.075439195.5
26394.8397.194582
35094.84--
CIFAR-10: модель c34
mse19984.480.175----
211484.13--
mae110484.130.09----
210884.31--
mape111184.310.04----
213984.39--
msle111584.310.14----
213984.59--
sh111084.670.13----
216984.41--
h117284.320.215----
214084.75--
ch110284.920.175----
215184.57--
lc114884.620.175----
216184.52--
cce116184.590.085----
217084.76--
scce111884.400.01----
218084.38--
bce116784.960.175----
215785.31--
kld112184.560.21----
216984.98--
pss110084.570.035----
210384.50--
cp111884.480.045----
215084.39--
ck18084.640.095----
215884.83--
mk18084.650.11----
217384.87--
CIFAR-10: модель ResNet20v1
mse113990.080.17----
215880.42--
mae115985.0910.83----
219667.43--
mape113269.205.52----
215180.24--
msle118889.110.39----
216089.89--
sh116790.140.26----
218689.62--
h116069.067.50----
215584.06--
ch114888.100.74----
218789.58--
lc115789.150.175----
213788.80--
cce117390.700.215----
213290.88--
314590.45--
scce115490.660.245----
215590.98--
315490.49--
bce115791.300.22----
213491.05--
315091.49--
kld113891.100.25----
218790.81--
312590.60--
pss118191.150.235----
215691.62--
312691.41--
414391.37--
cp113590.720.38----
215191.35--
316690.59--
414290.78--
ck113790.230.08----
218390.39--
mk119190.590.175----
210490.24--

В нашем случае измерение – это обучение НС и последующие вычисления значений критериев.
Погрешность измерений вычислена по следующей формуле:

Δcrit = (critmax – critmin) / 2,

Замечание 1. В приведенной выше таблице значения val_acc и acc берутся из файлов историй обучения. В файлах, хранящих acc, значения меньше, чем значения acc, полученные после оценки (evaluate) обученной модели НС или выполнения прогноза (prerdict). Например, для модели 2c28(lc) в файле acc_EMNIST_lc_Adam_2c28.txt после эпохи 61 имеем:

acc = 97.25%

После этой эпохи веса модели сохранены в файле weights_EMNIST_lc_Adam_2c28.61-0.95.hdf5 (val_acc после этой эпохи равен 95.03%).
После загрузки весов и выполнения оценки модели на обучающих данных имеем:

acc = 98.12%

То есть для этой модели err_img = 1052, а не 4466, как указано в табл. 9.
Такое расхождение объясняется тем, что при обучении берутся текущие значения acc, а при оценке класс определяется по наибольшему значению в выходном векторе. Например, текущий выход (случай двух классов) для объекта второго класса – это [0.2, 0.8], то есть точность 80%. Но при прогнозировании на обученной модели этот по этому вектору классифицируемый объект будет отнесен ко второму классу, то есть так же, как и при выходе [0.0, 1.0] (точность классификации 100%).

Замечание 2. Для функции потерь binary crossentropy в случае метрики 'accuracy' имеем завышенные значения точности.
Например, после 73-й эпохи обучения модели c7(bce) имеем на MNIST (эпоха 73 является лучшей по критерию val_acc):

val_acc = 99.89%

Однако после загрузки весов этой модели и выполнения evaluate (или predict) получим:

val_acc = 99.44%

Чтобы получить реальную точность, с binary crossentropy (lossFunNum == 11) используется вдобавок метрика categorical_accuracy:

metrics = ['accuracy', keras.metrics.categorical_accuracy] if lossFunNum == 11 else ['accuracy']

Замечание 3. В [9] погрешность измерений val_acc на EMNIST указывается равной ±0.12%.

Значение погрешности измерений val_acc может быть употреблено для поиска одинаково эффективных функций потерь: ФП одинаково эффективны, если значения val_acc, полученные в результате применения этих ФП, отличаются на величину, не превышающую погрешности измерения val_acc.
При поиске эффективных по val_acc ФП следует пользоваться погрешностью, найденной для ФП с большими значениями val_acc.
Используем для расчета средней величины погрешности следующие данные:

Таким образом, в задаче поиска эффективных ФП можно сделать следующие оценки погрешности измерений val_acc:

По мере накопления данных эти оценки могут корректироваться.

Воспроизведение результатов обучения

Воспроизведение результатов эпизода обучения обеспечивает следующий код [10], который должен быть исполнен до начала создания НС и ее обучения:

# Флаг задания режима воспроизведения результатов
repeat_results = False # True
seedVal = 348
import numpy as np
np.random.seed(seedVal) # Задание затравки датчика случайных чисел
#
if repeat_results:
    print('Режим воспроизведения (повторяемости) результатов')
    import random as rn
    import tensorflow as tf
    import os
    from keras import backend as K
    # Код, необходимый для воспроизведения результата
    os.environ['PYTHONHASHSEED'] = '0'
    rn.seed(seedVal)
    tf.set_random_seed(seedVal)
    session_conf = tf.ConfigProto(intra_op_parallelism_threads = 1,
                                 inter_op_parallelism_threads = 1)
    sess = tf.Session(graph = tf.get_default_graph(), config = session_conf)
    K.set_session(sess)

В этом коде фиксируются затравки датчиков случайных чисел, значение системной переменной PYTHONHASHSEED и устанавливается в сессии tensorFlow однопоточность.

Обработка историй обучения

Обработка историй обучений позволяет:

Поиск лучших решений

После завершения эпизода история обучения и веса обученной модели НС сохраняются в папку, имя которой согласуется с именем модели НС. Так, истории обучения и веса модели c1 будут записаны в папку w_1, с2 – в папку w_2 и так далее.
Лучшее решение ищется по критерию err_img, val_acc или acc.
При поиске просматриваются все файлы с историями обучения и в результате определяются модель НС и функция потерь, обеспечившие лучшее значение выбранного критерия обучения, а также эпоха, после завершения которой это значение было получено.
Дополнительно для этой модели выводятся сведения об эпохах с наименьшими потерями loss и val_loss, а также сведения об эпохах с наилучшими значениями двух других критериев обучения.
Результаты поиска лучших решений по всем наборам данных и историям обучения приведены в табл. 11.

Таблица 11. Лучшие решения

Лучшее
решение
Критерий поиска
и другие критерии
Описание эпохи
acc val_acc lossval_losserr_img
MNIST
c6(kld, err_img = 56, 61) Эпоха лучшего решения по err_img6199.9983399.450.0002690.03785756
Эпоха наибольшей точности accто же
Эпоха наибольшей точности val_accто же
Эпоха наименьших loss-потерь12099.9983399.420.0002690.0480959
Эпоха наименьших val_loss-потерь699.3199.190.0206680.024443495
2c12_2(mse, val_acc = 99.6, 43) Эпоха лучшего решения по val_acc4399.8583399.60.000280.000721125
Эпоха наибольшей точности acc6199.9283399.480.0001970.00091396
Эпоха наименьших loss-потерь5899.9116799.540.0001870.00088799
Эпоха наименьших val_loss-потерь4399.8583399.60.000280.000721125
Эпоха с наименьшим err_img6599.9183399.570.0002330.00072592
c6(kld, acc = 99.99833, 61)Лучшие решения по acc и err_img совпадают
EMNIST
c3(pss, err_img = 1555, 71) Эпоха лучшего решения по err_img7199.8517693.410.0387780.059431555
Эпоха наибольшей точности accто же
Эпоха наибольшей точности val_acc1497.7435993.950.0405610.0482664075
Эпоха наименьших loss-потерь6299.8197193.560.0387750.0592361564
Эпоха наименьших val_loss-потерь695.8165193.870.0427420.0458636497
2c28(lc, val_acc = 95.03, 61) Эпоха лучшего решения по val_acc6197.249295.030.0007860.0014014466
Эпоха наибольшей точности acc11997.8565794.590.0006660.00153800
Эпоха наименьших loss-потерь11597.8365494.720.000650.0014783798
Эпоха наименьших val_loss-потерь4396.8461594.880.000910.001385000
Эпоха с наименьшим err_img11697.8477694.790.0006570.001463769
c3(pss, acc = 99.85176, 71)Лучшие решения по acc и err_img совпадают
CIFAR-10
res_net(kld, err_img = 1305, 177) Эпоха лучшего решения по err_img17799.290.950.15160.49931305
Эпоха наибольшей точности accто же
Эпоха наибольшей точности val_acc13799.0891.100.15860.49481386
Эпоха наименьших loss-потерь19799.1890.980.15100.49951314
Эпоха наименьших val_loss-потерь8396.5790.350.26380.47792682
res_net(pss, val_acc = 0.9162, 155) Эпоха лучшего решения по val_acc15598.2691.620.11720.14331706
Эпоха наибольшей точности acc18498.4491.480.11670.14351634
Эпоха наименьших loss-потерь18998.4191.450.11670.14351649
Эпоха наименьших val_loss-потерь13797.9591.390.11830.14271885
Эпоха с наименьшим err_imgто же, что и для acc
c4(scce, acc = 0.99998, 63) Эпоха лучшего решения по acc6399.99871.10.00042.5042891
Эпоха наибольшей точности val_acc7199.99871.230.0012.1592878
Эпоха наименьших loss-потерьто же, что и для acc
Эпоха наименьших val_loss-потерь1178.2569.930.61710.892113881
Эпоха с наименьшим err_imgто же, что и для val_acc

Замечание. Единственно ошибочно классифицируемое изображение из обучающей выборке MNIST моделью c6(kld) имеет индекс 59915 и в массиве меток y_train помечено как число 4:

y_train[59915] = 4

Это изображение (рис. 11) классифицируется моделью c6(kld) как число 7.

4, а не 7

Рис. 11. Модель c6(kld) классифицирует это изображение как цифру 7

Поиск лучших решений по выбранному критерию выполняется в следующем порядке:

Входные данные:

Выходные данные: Список с описаниями лучших решений. Описание решения содержит:

  1. Перебрать все истории обучения и найти список solve_list лучших решений по заданному критерию.
  2. Если c_name = 'corr_img' Тогда
        Упорядочить solve_list по убыванию val_acc и возрастанию loss.
    Иначе
        Упорядочить solve_list по убыванию corr_img и возрастанию loss.
    КонецЕсли
    Взять первое решение из упорядоченного solve_list.
  3. Вывести описание найденного решения.
  4. Найти и вывести для найденной модели описания лучших эпох по двум другим критериям.
  5. Найти и вывести для найденной модели описания лучших эпох по loss и val_loss.

Пример. Результат поиска лучшего решения на MNIST по corr_img:

Критерий: число ошибочно классифицированных изображений на обеих выборках
Число сканируемых директорий: 19
Набор данных: MNIST
Всего изображений: 70000
Лучшие решения:

1.
['val_acc_MNIST_kld_Adam_c6.txt', 'w_6', 'kld']
Всего эпох: 120
Описание эпох:
('Эпоха лучшего решения по err_img:', 60, 99.99833, 99.45, 0.000269, 0.037857, 56)
('Эпоха наибольшей точности acc:', 60, 99.99833, 99.45, 0.000269, 0.037857, 56)
('Эпоха наибольшей точности val_acc:', 60, 99.99833, 99.45, 0.000269, 0.037857, 56)
('Эпоха наименьших loss-потерь:', 119, 99.99833, 99.42, 0.000269, 0.04809, 59)
('Эпоха наименьших val_loss-потерь:', 5, 99.31, 99.19, 0.020668, 0.024443, 495)

Программа поиска лучших решений приведена в прил. 5.

Потенциально лучшие решения

По результатам обучения можно выявить решения, близкие к решениям, приведенным в табл. 11.
Примеры решений на MNIST, близких к решению c6(kld, err_img = 56, 61) приведены в табл. 12.

Таблица 12. Решение c6(kld, err_img = 56, 61) и близкие к нему решения на MNIST

Решениеerr_imgval_accacc
c6(kld, err_img = 56, 61)5699.4599.99833
c6(cce, err_img = 57, 51)5799.4499.99833
c6(ck, err_img = 57, 118)5799.4499.99833
c9(kld, err_img = 57, 119)5799.4499.99833
c9(pss, err_img = 57, 59)5799.4499.99833
c9(scce, err_img = 59, 61)5999.4299.99833
c4(cce, err_img = 60, 63)6099.4199.99833
c4(ck, err_img = 60, 110)6099.4199.99833

Верхняя граница критерия err_img при составлении списка берется исходя из погрешности оценки результата обучения по этому показателю. Аналогичные списки можно составить и по критериям val_acc и acc.
Назовем решения, присутствующие в подобных списках, потенциально лучшими.
Диаграммы потенциально лучших решений по критерию err_img для разных наборов данных приведены на рис. 12.

Потенциально лучшие решения

Рис. 12. Потенциально лучшие решения по критерию err_img

Изображенные на рис. 12 диаграммы построены по приведенным в табл. 13 данным.

Таблица 13. Потенциально лучшие решения по критерию err_img
MNISTEMNISTCIFAR-10
Решениеerr_imgРешениеerr_imgРешениеerr_img
c4(cce, err_img = 60, 62)
c4(ck, err_img = 60, 109)
c9(scce, err_img = 59, 60)
c6(cce, err_img = 57, 50)
c6(ck, err_img = 57, 117)
c9(kld, err_img = 57, 118)
c9(pss, err_img = 57, 58)
c6(kld, err_img = 56, 60)
 
60
60
59
57
57
57
57
56
 
c15(kld, err_img = 1712, 119)
c15(ck, err_img = 1708, 108)
c15(pss, err_img = 1690, 104)
c17(cce, err_img = 1689, 117)
c1(mk, err_img = 1671, 56)
c02(ck, err_img = 1656, 59)
c25(cce, err_img = 1632, 98)
c3(mk, err_img = 1614, 68)
c3(pss, err_img = 1555, 70)
1712
1708
1690
1689
1671
1656
1632
1614
1555
res_net(kld, err_img = 1305, 177)
res_net(cce, err_img = 1312, 175)
res_net(scce, err_img = 1343, 157)
res_net(ck, err_img = 1424, 177)
res_net(mk, err_img = 1468, 179)
res_net(pss, err_img = 1634, 184)
res_net(bce, err_img = 1709, 181)
 
 
1305
1312
1343
1424
1468
1634
1709
 
 

Вывод диаграммы потенциально лучших решений выполняет процедура plotBestResultsDiag программы обработки историй обучения.
В табл. 14 показаны потенциально лучшие решения по критерию val_acc.

Таблица 14. Потенциально лучшие решения по критерию val_acc

MNISTEMNISTCIFAR-10
Решениеval_accРешениеval_accРешениеval_acc
3c12(cce, val_acc = 0.9957, 83)
2c12(msle, val_acc = 0.9957, 73)
2c12(bce, val_acc = 0.9957, 52)
3c12(bce, val_acc = 0.9957, 54)
2c12(msle, val_acc = 0.9957, 45)
3c12(ck, val_acc = 0.9957, 38)
3c12(kld, val_acc = 0.9957, 34)
2c12(scce, val_acc = 0.9957, 32)
2c12(ck, val_acc = 0.9958, 62)
2c12(mse, val_acc = 0.996, 42)
 
 
 
 
 
 
0.9957
0.9957
0.9957
0.9957
0.9957
0.9957
0.9957
0.9957
0.9958
0.9960
 
 
 
 
 
 
2c28(cce, val_acc = 0.94865, 63)
2c28(bce, val_acc = 0.94870, 90)
2c28(ck, val_acc = 0.94870, 86)
2c28(cp, val_acc = 0.94875, 73)
2c24(sh, val_acc = 0.94875, 73)
2c27(lc, val_acc = 0.94885, 49)
2c28(pss, val_acc = 0.94889, 103)
2c28(msle, val_acc = 0.94903, 52)
2c28(msle, val_acc = 0.94909, 73)
2c24(msle, val_acc = 0.94909, 28)
2c28(mse, val_acc = 0.94914, 52)
2c24(mk, val_acc = 0.94918, 85)
2c28(pss, val_acc = 0.94918, 68)
2c28(lc, val_acc = 0.94942, 93)
2c27(mse, val_acc = 0.94947, 42)
2c28(lc, val_acc = 0.95034, 60)
0.94865
0.94870
0.94870
0.94875
0.94875
0.94885
0.94889
0.94903
0.94909
0.94909
0.94914
0.94918
0.94918
0.94942
0.94947
0.95034
res_net(pss, val_acc = 0.9162, 155)
res_net(kld, val_acc = 0.911, 137)
res_net(bce, val_acc = 0.9105, 133)
res_net(cce, val_acc = 0.907, 172)
res_net(scce, val_acc = 0.9066, 153)
res_net(mk, val_acc = 0.9059, 190)
res_net(ck, val_acc = 0.9023, 136)
res_net(sh, val_acc = 0.9014, 166)
res_net(mse, val_acc = 0.9008, 138)
 
 
 
 
 
 
 
0.9162
0.9110
0.9105
0.907
0.9066
0.9059
0.9023
0.9014
0.9008
 
 
 
 
 
 
 

Выделение эффективных функций потерь

Приведенные в табл. 13 решения получены с применением разных функций потерь. Все они могут быть отнесены к числу наиболее эффективных функций потерь.
Составим по табл. 13 для MNIST список функций потерь:

[ [kld, 2, 56.5], [cce, 2, 58.5], [ck, 2, 58.5], [pss, 1, 57], [scce, 1, 59] ].

Элемент списка записан по следующему шаблону:

[<ФП>, <число появлений ФП в СПЛР>, <усредненное значение критерия обучения>],    (*)

где ФП – функция потерь;

СПЛР – список потенциально лучших решений.

Таким образом, для обучения на MNIST по критерию err_img могут быть рекомендованы следующие функции потерь: kld, cce, ck, pss и scce.
Составим по историям обучения списки эффективных функций потерь для всех наборов данных и всех критериев обучения, используя следующий алгоритм:

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

  1. Получить СПЛР и описания решений.
  2. Сформировать, используя шаблон (*), список функций потерь (СФП), обеспечивших получение потенциально лучших решений.
  3. Упорядочить СФП по числу их появления в СПЛР и усредненному значению критерия обучения.
  4. Предъявить упорядоченный СФП в качестве результата.

Функции потерь, выявленные по историям обучения моделей НС, приведены в табл. 15.

Таблица 15. Эффективные функции потерь

Набор данныхКритерийНижняя граница
критерия
Всего
решений
[Функция потерь,
число повторов,
значение критерия]
MNISTerr_img6022['cce', 3, 55.9997]
['kld', 3, 55.9997]
['ck', 3, 57.9999]
['bce', 2, 49.5]
['pss', 2, 54.4995]
['scce', 2, 54.5]
['lc', 1, 48.0]
['mse', 1, 53.0]
['msle', 1, 55.0]
['h', 1, 56.0]
['mk', 1, 56.0]
['mape', 1, 57.0]
['sh', 1, 59.0]
val_acc99.5728['ck', 3, 99.59]
['kld', 3, 99.58]
['scce', 3, 99.58]
['cce', 3, 99.58]
['msle', 3, 99.57]
['bce', 2, 99.61]
['pss', 2, 99.6]
['mse', 2, 99.59]
['mape', 1, 99.64]
['sh', 1, 99.63]
['h', 1, 99.62]
['mk', 1, 99.62]
['lc', 1, 99.61]
['mae', 1, 99.6]
['ch', 1, 99.6]
acc99.90172['ck', 18, 99.9585]
['cce', 18, 99.9577]
['scce', 18, 99.956]
['kld', 17, 99.9631]
['pss', 17, 99.9619]
['mk', 17, 99.9526]
['bce', 15, 99.9627]
['mse', 13, 99.9326]
['msle', 10, 99.9318]
['sh', 9, 99.9185]
['cp', 8, 99.9235]
['lc', 7, 99.9298]
['mape', 2, 99.9567]
['h', 1, 99.9717]
['mae', 1, 99.97]
['ch', 1, 99.9683]
EMNISTerr_img175011['mk', 3, 1666.3333]
['cce', 3, 1681.6667]
['pss', 2, 1622.5]
['ck', 1, 1708.0]
['kld', 1, 1712.0]
['scce', 1, 1718.0]
val_acc94.8739['lc', 5, 95.07]
['msle', 5, 95.06]
['mse', 4, 95.08]
['pss', 4, 95.03]
['mk', 3, 95.13]
['ck', 3, 95.05]
['scce', 2, 95.2]
['bce', 2, 95.18]
['kld', 2, 95.16]
['cce', 2, 95.13]
['sh', 2, 95.02]
['h', 1, 95.24]
['mae', 1, 95.21]
['ch', 1, 95.19]
['mape', 1, 95.13]
['cp', 1, 94.88]
acc98.25104['mk', 11, 99.3056]
['cce', 10, 99.2482]
['kld', 10, 99.2074]
['mse', 9, 98.7293]
['lc', 9, 98.677]
['cp', 9, 98.5375]
['pss', 8, 99.4253]
['ck', 8, 99.3027]
['scce', 8, 99.1779]
['msle', 8, 98.7655]
['bce', 7, 98.9182]
['sh', 7, 98.7436]
CIFAR-10err_img20007['kld', 1, 1305]
['cce', 1, 1312]
['scce', 1, 1343]
['ck', 1, 1424]
['mk', 1, 1468]
['pss', 1, 1634]
['bce', 1, 1710]
val_acc90.009['pss', 1, 91.62]
['kld', 1, 91.1]
['bce', 1, 91.05]
['cce', 1, 90.7]
['scce', 1, 90.66]
['mk', 1, 90.59]
['ck', 1, 90.23]
['sh', 1, 90.14]
['mse', 1, 90.08]
acc97.00101['mk', 11, 98.5062]
['kld', 11, 98.4329]
['cce', 11, 98.4042]
['scce', 11, 98.3204]
['ck', 10, 98.4912]
['bce', 10, 98.3962]
['pss', 10, 98.2378]
['mse', 8, 97.895]
['msle', 7, 97.9791]
['lc', 7, 97.8137]
['cp', 3, 98.6267]
['sh', 2, 97.349]

Диаграммы эффективности функций потерь

Диаграммы строятся по историям обучения после задания набора данных и вида критерия обучения.
Возможны три вида диаграмм:

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

Возможны три варианта построения диаграмм двух первых видов:

  1. По потенциально лучшим решениям по всем моделям НС.
  2. По потенциально лучшим решениям для каждой отдельно взятой модели НС.
  3. По всем решениям для всех моделей НС.

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

Частотная диаграмма эффективности функций потерь

Рис. 13. Частотные диаграммы эффективности функций потерь:
а – по потенциально лучшим решениям для всех моделей НС (mean_eff_all = False, one_dir = 0, showFreqDiag = True);
б – по потенциально лучшим решениям каждой модели НС (mean_eff_all = False, one_dir = 2, showFreqDiag = True)

Диаграммы, показанные на рис. 13, а, построены по данным, приведенным в табл. 16.

Таблица 16. Частота появления функций потерь в списке потенциально лучших решений для всех моделей НС

MNISTEMNISTCIFAR-10
['kld', 2]
['cce', 2]
['ck', 2]
['pss', 1]
['scce', 1]
 
 
['pss', 2]
['mk', 2]
['cce', 2]
['ck', 2]
['kld', 1]
 
 
['kld', 1]
['cce', 1]
['scce', 1]
['ck', 1]
['mk', 1]
['pss', 1]
['bce', 1]

Диаграммы, показанные на рис. 13, б, построены по данным, приведенным в табл. 17.

Таблица 17. Частота появления функций потерь в списке потенциально лучших решений для каждой модели НС

MNISTEMNISTCIFAR-10
['bce', 21]
['cce', 21]
['ck', 21]
['kld', 21]
['mk', 21]
['pss', 21]
['scce', 21]
['lc', 20]
['mse', 20]
['msle', 20]
['sh', 20]
['cp', 19]
['ch', 17]
['h', 17]
['mae', 17]
['mape', 17]
['ck', 31]
['cce', 30]
['kld', 30]
['mk', 30]
['pss', 30]
['scce', 29]
['msle', 28]
['bce', 27]
['lc', 27]
['mse', 27]
['sh', 26]
['cp', 23]
['ch', 2]
['h', 1]
['mae', 1]
['mape', 1]
['bce', 14]
['cce', 14]
['scce', 14]
['ck', 13]
['mk', 13]
['msle', 13]
['kld', 12]
['lc', 12]
['mse', 12]
['pss', 12]
['cp', 10]
['sh', 10]
['mape', 5]
['ch', 3]
['h', 3]
['mae', 3]

На рис. 14 показаны средневзвешенные диаграммы для решений, полученных на разных наборах данных; по оси абсцисс так же отложены имена функций потерь, а по оси ординат – значения критерия обучения, усредненные для лучших (рис. 14, а) и всех (рис. 14, б) решений.

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

Рис. 14. Средневзвешенные диаграммы эффективности функций потерь на всех моделях НС по критерию обучения err_img:
а – отобраны только лучшие решения (mean_eff_all = False, one_dir = 0);
б – взяты все решения (mean_eff_all = True, one_dir = 0)

Перед выводом диаграммы (рис. 14) данные сортируются по убыванию err_img.

На диаграммах (рис. 15) показана эффективность функций потерь, примененных на EMNIST для модели 2c28, по критерию val_acc; по оси абсцисс отложены имена функций потерь, а по оси ординат – значения критерия обучения.

Диаграмма эффективности функций потерь на одной модели НС

Рис. 15. Диаграмма эффективности функций потерь для 2c28 на EMNIST по критерию val_acc:
а – только лучшие решения;
б – все решения

Вывод диаграмм обеспечивает процедура plotDiags программы обработки историй обучения.

Графики обучения

На рис. 16-18 показаны графики обучения (изменения точности и потерь) для лучших решений на EMNIST по критериям err_img, val_acc и acc.
По оси абсцисс отложены номера эпох, а по оси ординат – либо точность классификации, либо потери.
Лучшее решение выбрано после обучения 30 моделей. Каждая модель обучалась на EMNIST с применением 16-и функций потерь.

График обучения лучшего решения по критерию err_img

Рис. 16. График обучения лучшего решения по критерию err_img

График обучения лучшего решения по критерию val_acc

Рис. 17. График обучения лучшего решения по критерию val_acc

График обучения лучшего решения по критерию acc

Рис. 18. График обучения лучшего решения по критерию acc

Из приведенных графиков видно, что НС, выбранные по критериям err_img (рис. 16) и acc (рис. 18), являются переобученными (снижение потерь на обучающем множестве сопровождается ростом потерь на оценочном множестве). Устранение причин, вызывающих переобучение, приведет к росту качества обучения по всем трем используемым критериям.

Вывод графиков обучения выполняет следующая процедура:

def plot_hist(is_acc, sva, crit_list, pref_list, imgType, crit, d):
    import matplotlib.pyplot as plt # Например, d = ' / '
    # crit_list:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    fn = crit_list[7]
    dr = crit_list[8]
    fun = crit_list[9]
    p = crit_list[10]
    base = fn[bias:]
    ttl = imgType + d + crit + d + fun + d + dr
    if is_acc:
        ks = 0
        lb = 'acc'
        lb2 = 'val_acc'
        loc = 'lower right'
    else:
        ks = 1
        lb = 'loss'
        lb2 = 'val_loss'
        loc = 'center right'
    acc_loss = readFile(p + pref_list[ks] + base)
    val_acc_loss = readFile(p + pref_list[ks + 2] + base)
    plt.figure(figsize = (5, 2))
    plt.plot(acc_loss, color = 'r', label = lb, linestyle = '--')
    plt.plot(val_acc_loss, color = 'g', label = lb2)
    plt.title(ttl)
    plt.legend(loc = loc)
    plt.show()

Характеристики историй обучения

Определяются следующие характеристики историй обучения:

Эти значения можно находить как для потенциально лучших решений по заданному критерия обучения, так и для выбранного решения.
Поиск выполняется в случае критерия val_acc или acc по истории обучения, отвечающей текущему решению. В случае критерия corr_img предварительно создается список, содержащий значения критерия для текущего решения.
Сведения о наибольших расстояниях между эпохами двух последовательных обновлений максимума критерия обучения для различных наборов данных и критериев обучения приведены в табл. 18.

Таблица 18. Наибольшие расстояния между эпохами двух последовательных обновлений максимума критерия обучения

РешениеНаибольшее расстояниеНомера эпох обновления
максимума критерия
Значение критерия обучения
в этих эпохах
MNIST
c6(ck, err_img = 57, 118)3840 / 7860 / 59
2c12_2(msle, val_acc = 99.57, 74)2945 / 7499.55 / 99.57
c13(mk, acc = 99.967, 120)3875 / 11399.957 / 99.958
EMNIST
c3(pss, err_img = 1555, 71)962 / 711564 / 1555
2c28(cp, val_acc = 94.875, 74)4529 / 7494.817 / 94.875
c4(ck, acc = 99.681, 76)1264 / 7699.621 / 99.681
CIFAR-10
res_net(bce, err_img = 1710, 182)17165 / 1821741 / 1710
res_net(bce, val_acc = 91.05, 134)4833 / 8184.27 / 84.35
c1(mk, acc = 98.80, 100)1882 / 10098.77 / 98.80

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

Таблица 19. Наибольшие разницы между двумя последними обновлениями максимума критерия обучения

РешениеНаибольшая разницаНомера эпох обновления
максимума критерия
Значение критерия обучения
в этих эпохах
MNIST
c9(sсce, err_img = 59, 118)1129 / 3170 / 59
2c12_2(mse, val_acc = 99.6, 43)0.0937 / 4399.51 / 99.6
c4(scce, acc = 99.985, 25)0.07723 / 2599.908 / 99.985
EMNIST
c15(pss, err_img = 1690, 105)109104 / 1051799 / 1690
2c24(msle, val_acc = 94.91, 29)0.22625 / 2994.68 / 94.91
c1(mk, acc = 99.794, 57)0.151 / 5799.694 / 99.794
CIFAR-10
res_net(scce, err_img = 1343, 158)42153 / 1581385 / 1343
res_net(bce, val_acc = 91.05, 134) 0.18131 /134 90.87 / 91.05
c4(ck, acc = 99.68, 34)0.55233 / 3499.132 / 99.68

Замечание. В случае corr_img выводится err_img = all_img – corr_img, где all_img – число изображений в наборе данных.

Число обновлений максимума критерия обучения определяется для потенциально лучших решений выбранной модели НС. Эта характеристика для модели c14 и критерия val_acc приведена в табл. 20.

Таблица 20. Число обновлений максимума val_acc при обучении модели c14

Функция потерьЧисло обновлений максимума val_accВсего эпохval_acc, %
MNIST
pss812099.49
msle9- '' -99.51
cce10- '' -99.50
cp11- '' -99.52
ck13- '' -99.51
sh13- '' -- '' -
mse13- '' -99.49
lc13- '' -- '' -
kld14- '' -99.51
EMNIST
mk116094.50
ck11- '' -94.47
lc12- '' -94.62
cce12- '' -94.52
pss12- '' -94.45
sh13- '' -94.53
msle13- '' -94.50
scce14- '' -94.52
mse15- '' -94.62
kld17- '' -94.47
CIFAR-10
ch12030.62
bce1201573.56
ck1201873.71
cp1201973.34
scce1201973.13
lc1202072.40
cce1202074.1
mk1202073.88
kld1162073.61
pss1202073.57
msle1202072.69
sh1202472.68
h1203672.03
mae1204271.50
mape1204371.92

На рис. 19 показана диаграмма максимальных расстояний между эпохами двух последовательных обновления максимума критерия acc при обучении разных моделей на MNIST с функцией kld (как и ранее берутся потенциально лучшие решения).

Расстояния между эпохами обновления максимума критерия acc

Рис. 19. Максимальные расстояния между эпохами обновления максимума критерия acc (MNIST, лучшие решения)

На рис. 20 приведена диаграмма максимальных разниц значений критерия acc двух последовательных обновления его максимума при обучении разных моделей на MNIST с функцией kld (как и ранее берутся потенциально лучшие решения).

Разница между значениями критерия acc при последних обновления его максимума

Рис. 20. Разница между значениями критерия acc при последних обновлениях его максимума (MNIST, лучшие решения)

На рис. 21 приведены диаграммы числа обновлений максимума критерия val_acc, построенные по табл. 14.

Число обновлений максимума критерия val_acc

Рис. 21. Число обновлений максимума критерия val_acc

Приведенные результаты позволяет получить процедура find_history_values программы обработки историй обучения.

Модели НС с близкими списками эффективных функций потерь

Такие модели выявляются по историям обучения после задания набора данных, критерия обучения и коэффициента близости списков эффективных функций потерь
Порядок обработки данных по следующей схеме:

  1. Для каждой модели по историям ее обучения формируется список эффективных функции потерь – функций, обеспечивающих потенциально лучшие решения.
  2. Для каждой пары моделей НС выполняется сравнение их списков эффективных функции потерь. Если списки близки, то выводятся имена моделей, списки эффективных функций, обозначения моделей и число параметров в модели.

Два списка считаются близкими, если число n_eq одинаковых функций потерь в этих списках удовлетворяет следующему условию:

n_eq >= near * (len1 + len2) / 2,

где near – коэффициент близости списков;
len1, len2 – длины списков.

Результаты для различных наборов данных с критерием обучения err_img – число ошибочно классифицированных изображений:

MNIST. Всего моделей: 19. near = 0.85:
1
['w_6', 'w_4']
[['cce', 'ck', 'kld']]
[['cce', 'ck', 'kld']]
CR20-P-CR30-P-F-DR600 / 898'580
CR20-P-CR30-P-F-DR600-DL30 / 910'910
2
['w_9', 'w_1']
[['kld', 'pss', 'scce']]
[['kld', 'pss', 'scce']]
CR20-P-CR30-P-F-DR600-DL16 / 902'356
CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 / 3'276'724
3
['w_12', 'w_17']
[['cce']]
[['cce']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'356
CR32-P-CR64-P-F-0.25-DR800-DL30 / 2'567'316
4
['2w_12', '3w_12']
[['cp', 'mse']]
[['cp', 'mse']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'356 * 2
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'356 * 3
Список моделей, не имеющих моделей с близким списком эффективных функций потерь:
['w_5', 'w_7', 'w_8', 'w_11', '2w_12_2', 'w_13', 'w_14', 'w_10', 'w_15', 'w_16', 'w_18']
Длина списка: 11
Всего моделей: 19

EMNIST. Всего моделей: 30. near = 0.85:
1
['w_19', 'w_28']
[['kld', 'mk', 'scce']]
[['kld', 'mk', 'scce']]
CR10-P-CR15-P-F-0.25-DR300 / 231'211
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216
2
['w_9', 'w_24']
[['ck', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-DR600-DL16 / 902'628
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026
3
['w_12', 'w_1']
[['mk']]
[['mk']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'628
CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 / 3'277'220
4
['2w_12', '3w_12']
[['cp', 'msle']]
[['cp', 'msle']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'628 * 2
CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 902'628 * 3
5
['w_13', 'w_14']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 911'406
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 911'406
6
['w_13', 'w_24']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 911'406
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026
7
['w_13', 'w_26']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 911'406
CR20-P-CR30-P-F-0.3-DR600-DL60 / 930'216
8
['w_13', 'w_15']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 911'406
CR32-P-CR64-P-F-0.25-DR800-DL16 / 2'556'234
9
['w_14', 'w_24']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 911'406
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026
10
['w_14', 'w_26']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 911'406
CR20-P-CR30-P-F-0.3-DR600-DL60 / 930'216
11
['w_14', 'w_15']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'pss', 'scce']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 911'406
CR32-P-CR64-P-F-0.25-DR800-DL16 / 2'556'234
12
['w_24', 'w_26']
[['cce', 'ck', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026
CR20-P-CR30-P-F-0.3-DR600-DL60 / 930'216
13
['w_26', 'w_15']
[['cce', 'ck', 'kld', 'mk', 'pss', 'scce']]
[['cce', 'ck', 'kld', 'pss', 'scce']]
CR20-P-CR30-P-F-0.3-DR600-DL60 / 930'216
CR32-P-CR64-P-F-0.25-DR800-DL16 / 2'556'234
14
['w_27', 'w_02']
[['ck', 'mk']]
[['ck', 'mk']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60 / 930'216
CR32-P-CR64-P-F-0.35-DR1024-0.2-DL16 / 3'262'506
15
['2w_24', '2w_27']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60 / 930'216 * 2
16
['2w_24', '2w_28']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
17
['2w_24', '2w_28_2']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 / 949'026 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
18
['2w_27', '2w_28']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60 / 930'216 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
19
['2w_27', '2w_28_2']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60 / 930'216 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
20
['2w_28', '2w_28_2']
[['cp']]
[['cp']]
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / 930'216 * 2
Список моделей, не имеющих моделей с близким списком эффективных функций потерь:
['w_5', 'w_7', 'w_20', '2w_20', '3w_20', 'w_21', 'w_11', 'w_4', 'w_10', 'w_25', 'w_17', 'w_3']
Длина списка: 12
Всего моделей: 30

CIFAR-10. Всего моделей: 15. near = 0.95:
1
['w_4', 'w_13']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-DL30 / 1'176'930
2
['w_4', 'w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930
3
['w_4', '2w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 2
4
['w_4', '3w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 3
5
['w_13', 'w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930
6
['w_13', '2w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 2
7
['w_13', '3w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 3
8
['w_14', '2w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 2
9
['w_14', '3w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 3
10
['2w_14', '3w_14']
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'kld', 'lc', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 2
CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 1'176'930 * 3
11
['w_17', 'w_18']
[['bce', 'cce', 'ch', 'ck', 'cp', 'h', 'kld', 'lc', 'mae', 'mape', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
[['bce', 'cce', 'ck', 'cp', 'h', 'kld', 'lc', 'mae', 'mape', 'mk', 'mse', 'msle', 'pss', 'scce', 'sh']]
CR32-P-CR64-P-F-0.25-DR800-DL30 / 3'321'332
CR32-P-CR64-P-F-0.25-DR800-0.2-DL30 / 3'321'332
Список моделей, не имеющих моделей с близким списком эффективных функций потерь:
['w_5', 'w_7', 'w_15', 'w_16', 'w_33', 'w_34', 'w_1', 'res_net']
Длина списка: 8
Всего моделей: 15

Пересечение списков неверно классифицированных изображений на тестовых данных

Пересечение списков неверно классифицированных изображений выполняется на тестовой выборке.
Эта операция позволяет, во-первых, установить, какие изображения не смогла верно классифицировать ни одна из примененных моделей НС, во-вторых, выделить трудно классифицируемые изображения, а в-третьих, оценить предельно достижимую точность классификацию тестовых изображений рассматриваемого набора данных.
По аналогии с множествами пересечение списков A и B – это список, содержащий элементы, имеющиеся и в A и в B.
Список неверно классифицированных изображений получается в следующей последовательности:

  1. Выбрать модели НС с наилучшими значениями точности классификации.
  2. После обучения на этапе тестирования для выбранных модели НС в текстовые файлы выгрузить списки номеров неверно классифицированных изображений.
  3. В отдельной программе эти файлы загрузить в списки и найти пересечение этих списков.

В случае MNIST были выбраны следующие решения:

В случае EMNIST пересечение 69 списков с индексами неверно классифицированных изображений на тестовой выборке различными моделями НС с критерием val_acc в диапазоне [95.03 – 94.7] дает список длинной 342.

Замечание. При val_acc = 95.03 неверно классифицируются 1034 изображения тестовой выборки, а при val_acc = 94.7 – 1102 изображения.

Код, обеспечивающий выгрузку в файл err_pics.txt индексов неверно классифицированных изображений MNIST или EMNIST:

def saveErrPics(model, X_test, y_test, y_test_0):
    # model - модель НС
    # X_test - массив тестовых данных
    # y_test_0 - массив меток (номеров классов) меток тестовых данных
    # y_test - массив с категориальным представлением меток тестовых данных
    file_name = 'err_pics.txt'
    n_test = len(y_test)
    y_pred = model.predict(X_test) # Прогнозирование
    classes = []
    for m in y_pred:
        classes.append(np.argmax(m))
    # np.sum(classes == y_test_0) вернет сумму случаев, когда classes[i] = y_test_0[i]
    nClassified = np.sum(classes == y_test_0)
    nNotClassified = n_test - nClassified # Число неверно классифицированных образов
    fp = open(file_name, 'w')
    n = 0
    for i in range(n_test):
        s_true = names[y_test_0[i]]
        s_pred = names[classes[i]]
        if s_true != s_pred:
            n += 1
            if (n > nNotClassified): break
            fp.write(str(i) + '\n')
    fp.close()

Код, выполняющий загрузку файлов со списками индексов неверно классифицированных образов и находящий пересечение этих списков:

p0 = 'G:/AM/НС/errPics/'
printErr = False
nnTypeNum = 1 # 1 - conv; 2 - mlp; 3 - lstm
dataSetNum = 1 # 1 - MNIST; 2 - EMNIST; 3 - CIFAR10
#
# Пересечение списков неверно классифицированных изображений
def loadErr(fn):
    with open(pathToErrData + fn, 'r') as f:
        err = f.read()
    err = err.split('\n')
    if err.index('') > 0: err.remove('')
    return err
#
def crossErr(err, err2):
    errPics = []
    for s in err:
        if len(s) > 0 and s in err2:
            errPics.append(s)
    return errPics
nf = 69
if nnTypeNum == 1:
    if dataSetNum == 1:
        pathToErrData = p0 + 'mnist/'
        fn1 = 'err_MNIST_cce_kld_scce_Adam_2c12_38' + '.txt'
        fn2 = 'err_MNIST_scce_Adam_2c12_39' + '.txt'
        fn3 = 'err_MNIST_mse_Adam_2c12_40' + '.txt'
        fn4 = 'err_MNIST_cce_kld_Adam_2c12_42' + '.txt'
        fn5 = 'err_MNIST_msle_Adam_2c12_43' + '.txt'
        fn6 = 'err_MNIST_cce_Adam_c13_46' + '.txt'
        fn7 = 'err_MNIST_mse_Adam_c13_47' + '.txt'
        fn8 = 'err_MNIST_mae_Adam_c11_49' + '.txt'
        fn9 = 'err_MNIST_h_Adam_2c12_50' + '.txt'
        fn10 = 'err_MNIST_bce_Adam_c11_50' + '.txt'
        fn11 = 'err_MNIST_pss_Adam_c11_50' + '.txt'
        fn12 = 'err_MNIST_sh_Adam_c11_50' + '.txt'
        fn13 = 'err_MNIST_msle_Adam_c11_51' + '.txt'
        fn14 = 'err_MNIST_mape_Adam_c11_52' + '.txt'
    else:
        pathToErrData = p0 + 'emnist/'
        fn1 = 'err_EMNIST_lc_Adam_2c28_1033' + '.txt'
        fn2 = 'err_EMNIST_mse_Adam_2c27_1051' + '.txt'
        fn3 = 'err_EMNIST_lc_Adam_2c28_2_1052' + '.txt'
        fn4 = 'err_EMNIST_pss_Adam_2c28_2_1057' + '.txt'
        fn5 = 'err_EMNIST_mse17_kld_Adam_2c24_1057' + '.txt'
        fn6 = 'err_EMNIST_mse_Adam_2c28_1058' + '.txt'
        fn7 = 'err_EMNIST_msle_Adam_2c24_1059' + '.txt'
        fn8 = 'err_EMNIST_msle_Adam_2c28_2_1059' + '.txt'
        fn9 = 'err_EMNIST_msle_Adam_2c28_1060' + '.txt'
        fn10 = 'err_EMNIST_pss_Adam_2c28_1063' + '.txt'
        fn11 = 'err_EMNIST_lc_Adam_2c27_1064' + '.txt'
        fn12 = 'err_EMNIST_sh_Adam_2c24_1066' + '.txt'
        fn13 = 'err_EMNIST_cp_Adam_2c28_1066' + '.txt'
        fn14 = 'err_EMNIST_cce_kld_Adam_2c28_2_1067' + '.txt'
        fn15 = 'err_EMNIST_bce_Adam_2c28_1068' + '.txt'
        fn16 = 'err_EMNIST_cce_Adam_2c28_1068' + '.txt'
        fn17 = 'err_EMNIST_cce_kld_Adam_2c28_1069' + '.txt'
        fn18 = 'err_EMNIST_sh_Adam_2c28_2_1069' + '.txt'
        fn19 = 'err_EMNIST_cce_Adam_2c27_1070' + '.txt'
        fn20 = 'err_EMNIST_mse_Adam_2c28_2_1070' + '.txt'
        fn21 = 'err_EMNIST_pss_Adam_2c24_1071' + '.txt'
        fn22 = 'err_EMNIST_bce_Adam_2c24_1072' + '.txt'
        fn23 = 'err_EMNIST_mse_Adam_c28_1072' + '.txt'
        fn24 = 'err_EMNIST_msle_Adam_2c27_1072' + '.txt'
        fn25 = 'err_EMNIST_cce_kld_Adam_2c24_1073' + '.txt'
        fn26 = 'err_EMNIST_lc_Adam_2c24_1073' + '.txt'
        fn27 = 'err_EMNIST_kld_Adam_2c24_1075' + '.txt'
        fn28 = 'err_EMNIST_cp_Adam_2c24_1076' + '.txt'
        fn29 = 'err_EMNIST_sh_Adam_2c28_1076' + '.txt'
        fn30 = 'err_EMNIST_mse_Adam_2c24_1076' + '.txt'
        fn31 = 'err_EMNIST_msle_Adam_c24_1077' + '.txt'
        fn32 = 'err_EMNIST_cce_Adam_2c24_1078' + '.txt'
        fn33 = 'err_EMNIST_bce_Adam_2c27_1077' + '.txt'
        fn34 = 'err_EMNIST_cp_Adam_2c28_1077' + '.txt'
        fn35 = 'err_EMNIST_sh_Adam_2c27_1080' + '.txt'
        fn36 = 'err_EMNIST_scce_Adam_2c27_1079' + '.txt'
        fn37 = 'err_EMNIST_scce_Adam_2c28_1080' + '.txt'
        fn38 = 'err_EMNIST_kld_Adam_2c28_2_1080' + '.txt'
        fn39 = 'err_EMNIST_mse17_kld_Adam_2c27_1079' + '.txt'
        fn40 = 'err_EMNIST_bce_Adam_2c28_1082' + '.txt'
        fn41 = 'err_EMNIST_pss_Adam_2c27_1083' + '.txt'
        fn42 = 'err_EMNIST_sh_Adam_2c12_1083' + '.txt'
        fn43 = 'err_EMNIST_bce_Adam_c24_1084' + '.txt'
        fn44 = 'err_EMNIST_cce_Adam_2c12_1090' + '.txt'
        fn45 = 'err_EMNIST_mse17_kld_Adam_c24_1089' + '.txt'
        fn46 = 'err_EMNIST_sh_Adam_3c12_1091' + '.txt'
        fn47 = 'err_EMNIST_kld_Adam_c24_1092' + '.txt'
        fn48 = 'err_EMNIST_mse_Adam_c3_1093' + '.txt'
        fn49 = 'err_EMNIST_cp_Adam_2c28_1075_old' + '.txt'
        fn50 = 'err_EMNIST_bce_Adam_2c27_1080_old' + '.txt'
        fn51 = 'err_EMNIST_sh_Adam_3c12_1086_old' + '.txt'
        fn52 = 'err_EMNIST_cce_kld_Adam_3c12_1085_old' + '.txt'
        fn53 = 'err_EMNIST_cce_kld_Adam_2c12_1095_old' + '.txt'
        fn54 = 'err_EMNIST_mse_Adam_2c12_1096' + '.txt'
        fn55 = 'err_EMNIST_lc_Adam_c28_1096' + '.txt'
        fn56 = 'err_EMNIST_kld_Adam_c28_1096' + '.txt'
        fn57 = 'err_EMNIST_pss_Adam_2c28_1096_old' + '.txt'
        fn58 = 'err_EMNIST_kld_Adam_c28_1096' + '.txt'
        fn59 = 'err_EMNIST_cp_Adam_3c20_1097' + '.txt'
        fn60 = 'err_EMNIST_lc_Adam_c27_1098' + '.txt'
        fn61 = 'err_EMNIST_msle_Adam_3c20_1098' + '.txt'
        fn62 = 'err_EMNIST_myLoss_Adam_2c12_1098' + '.txt'
        fn63 = 'err_EMNIST_lc_Adam_2c12_1099' + '.txt'
        fn64 = 'err_EMNIST_cce_kld_Adam_3c12_1099' + '.txt'
        fn65 = 'err_EMNIST_cce_kld_Adam_2c12_1100' + '.txt'
        fn66 = 'err_EMNIST_lc_Adam_3c20_1101' + '.txt'
        fn67 = 'err_EMNIST_bce_Adam_3c12_1101' + '.txt'
        fn68 = 'err_EMNIST_mse_Adam_3c12_1102' + '.txt'
        fn69 = 'err_EMNIST_cp_Adam_3c20_1103' + '.txt'
elif nnTypeNum == 2:
    pathToErrData = p0 + 'mnist_mlp/'
    fn1 = 'err_MNIST_cce_kld_Adam_mlp_3' + '.txt'
    fn2 = 'err_MNIST_pss_Adam_mlp_3' + '.txt'
    fn3 = 'err_MNIST_bce_Adam_mlp_3' + '.txt'
    fn4 = 'err_MNIST_kld_Adam_mlp_3' + '.txt'
    fn5 = 'err_MNIST_bce_Adam_mlp_1' + '.txt'
    fn6 = 'err_MNIST_cce_kld_Adam_mlp_1' + '.txt'
    fn7 = 'err_MNIST_bce_Adam_mlp_2' + '.txt'
    fn8 = 'err_MNIST_myLoss_Adam_mlp_1' + '.txt'
    fn9 = '' + '.txt'
if dataSetNum == 1:
    lst_fn = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10,
             fn11, fn12, fn13, fn14]
else:
    lst_fn = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10,
             fn11, fn12, fn13, fn14, fn15, fn16, fn17, fn18, fn19, fn20,
             fn21, fn22, fn23, fn24, fn25, fn26, fn27, fn28, fn29, fn30,
             fn31, fn32, fn33, fn34, fn35, fn36, fn37, fn38, fn39, fn40,
             fn41, fn42, fn43, fn44, fn45, fn46, fn47, fn48, fn49, fn50,
             fn51, fn52, fn53, fn54, fn55, fn56, fn57, fn58, fn59, fn60,
             fn61, fn62, fn63, fn64, fn65, fn66, fn67, fn68, fn69]
nf = min(len(lst_fn), nf)
err1 = loadErr(fn1)
err2 = loadErr(fn2)
if printErr: print(err1)
if printErr: print(err2)
errPics = crossErr(err1, err2)
print('Число изображений в ' + fn1 + ':', len(err1))
print('Число изображений в ' + fn2 + ':', len(err2))
print('Будет выполнено пересечений:', nf)
print('Номер пересечения: 1')
print('Всего неверных классификаций', len(errPics))
for m in range(2, nf):
    fn = lst_fn[m]
    err = loadErr(fn)
    print('Число изображений в ' + fn + ':', len(err))
    if printErr: print(err)
    errPics = crossErr(errPics, err);
    print('Номер пересечения:', m)
    print('Всего неверных классификаций', len(errPics))
print('Список неверных классификаций:')
print(errPics)

Результат поиска пересечений в случае MNIST:

Число изображений в err_MNIST_cce_kld_scce_Adam_2c12_38.txt: 38
Число изображений в err_MNIST_scce_Adam_2c12_39.txt: 39
Номер пересечения: 1
Всего неверных классификаций 29
Число изображений в err_MNIST_mse_Adam_2c12_40.txt: 40
Номер пересечения: 2
Всего неверных классификаций 23
Число изображений в err_MNIST_cce_kld_Adam_2c12_42.txt: 42
Номер пересечения: 3
Всего неверных классификаций 20
Число изображений в err_MNIST_msle_Adam_2c12_43.txt: 43
Номер пересечения: 4
Всего неверных классификаций 19
Число изображений в err_MNIST_cce_Adam_c13_46.txt: 46
Номер пересечения: 5
Всего неверных классификаций 14
Число изображений в err_MNIST_mse_Adam_c13_47.txt: 47
Номер пересечения: 6
Всего неверных классификаций 11
Число изображений в err_MNIST_mae_Adam_c11_49.txt: 49
Номер пересечения: 7
Всего неверных классификаций 9
Число изображений в err_MNIST_h_Adam_2c12_50.txt: 50
Номер пересечения: 8
Всего неверных классификаций 7
Число изображений в err_MNIST_bce_Adam_c11_50.txt: 50
Номер пересечения: 9
Всего неверных классификаций 6
Число изображений в err_MNIST_pss_Adam_c11_50.txt: 50
Номер пересечения: 10
Всего неверных классификаций 4
Число изображений в err_MNIST_sh_Adam_c11_50.txt: 50
Номер пересечения: 11
Всего неверных классификаций 4
Число изображений в err_MNIST_msle_Adam_c11_51.txt: 51
Номер пересечения: 12
Всего неверных классификаций 2
Число изображений в err_MNIST_mape_Adam_c11_52.txt: 52
Номер пересечения: 13
Всего неверных классификаций 2
Список неверных классификаций:
['1014', '1901']

Вывод неверно классифицированных изображений обеспечивает следующий код:

import numpy as np
import matplotlib.pyplot as plt
#
errPics = [1014, 1901]
dataSetNum = 1 # 1 - MNIST; 2 - EMNIST
#
def makeNames(imgType):
    names = []
    if imgType == 'MNIST':
        for i in range(10): names.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
    elif imgType == 'EMNIST':
        for i in range(26): names.append(chr(65 + i)) # ['A', 'B', 'C', ..., 'Z']
    return names
def load_test(pathToData):
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    X_test = np.asarray(data2)
    y_test = np.asarray(labels2)
    return X_test, y_test, len(y_test)
#
# Путь к тестовым данным
p0 = 'G:/AM/НС/'
if dataSetNum == 1:
    pathToData = p0 + 'mnist/'; imgType = 'MNIST'
elif dataSetNum == 2:
    pathToData = p0 + 'emnist/'; imgType = 'EMNIST'
else: print('Плохой номер набора данных'); sys.exit()
#
X_test, y_test_0, n_test = load_test(pathToData)
if imgType == 'MNIST':
    X_test = X_test.reshape(n_test, 28, 28, 1)
elif imgType == 'EMNIST':
    X_test = X_test.reshape(n_test, 28, 28, 1).transpose(0,2,1,3)
    y_test_0 -= 1
names = makeNames(imgType)
n = 0
for m in errPics:
    n += 1
    s_true = names[y_test_0[m]]
    plt.subplot(1, 8, n)
    plt.imshow(X_test[m].reshape(28, 28), cmap = plt.get_cmap('gray'))
    plt.title(s_true)
    plt.axis('off')
plt.subplots_adjust(hspace = 0.5)
plt.show()

Изображения цифр 6 и 9 тестового набора данных MNIST, которые не смогли верно классифицировать примененные модели НС, показаны на рис. 22.

Неверно классифицированные изображения MNIST

Рис. 22. Изображения тестового набора данных MNIST, которые не смогли верно классифицировать примененные модели НС
Индексы изображений в массиве меток: 1014, 1901

Над изображением его истинная метка, снизу – метка, предсказанная c11(mae).
Другие модели НС могут давать иные прогнозы. Так, c01(bce) для изображения с индексом 1014 предсказала 0 вместо 6.

Примеры букв из EMNIST, ошибочно классифицируемых всеми моделями НС, показаны на рис. 23.

Неверно классифицированные изображения EMNIST

Рис. 23. Примеры изображений тестового набора данных EMNIST, которые не смогли верно классифицировать примененные модели НС

Объединение прогнозов

Изображения, которые не смогли верно классифицировать использованные НС, можно получить, если объединить прогнозы нескольких НС с различными функциями потерь и использовать этот объединенный прогноз для классификации изображения.
Возьмем, к примеру, прогнозы y_pred_1 и y_pred_2 СНС c11(mae) и c11(sh).
Объединение прогнозов y_pred_1 и y_pred_2 осуществляется по следующему правилу:

Если y_pred_1 != y_test и y_pred_2 = y_test Тогда
    y_pred = y_pred_2
Иначе
    y_pred = y_pred_1

В приведенном выше правиле y_test – это метка изображения (истинное значение класса изображения).
Так, если взять по три первых прогноза каждой из названных выше СНС:

Прогнозы из y_pred_MNIST_mae_Adam_conv_11.bin:

[0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00 0.0000000e+00 0.0000000e+00]
[0.0000000e+00 0.0000000e+00 1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00]
[5.5263964e-33 1.0000000e+00 2.4369681e-30 0.0000000e+00 5.4157616e-27
7.3043492e-34 1.3921112e-31 8.2266358e-28 3.2682385e-35 0.0000000e+00]

Прогнозы из y_pred_MNIST_sh_Adam_conv_11.bin:

[2.3635973e-28 7.1891472e-22 1.6735443e-27 3.3039484e-22 1.3014925e-30
7.5735469e-31 1.1090597e-36 1.0000000e+00 2.0680592e-33 1.0226212e-23]
[5.9359326e-24 1.6917469e-28 1.0000000e+00 0.0000000e+00 1.3270321e-35
0.0000000e+00 3.1075434e-27 2.4121234e-32 2.1794855e-34 9.4866112e-38]
[1.2166917e-17 1.0000000e+00 3.1378157e-17 6.6714030e-24 1.6553219e-12
2.2798540e-16 7.6239186e-18 2.4329525e-10 1.4236543e-20 5.9331766e-22]

Получим следующие итоговые прогнозы:

[0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00 0.0000000e+00 0.0000000e+00]
[0.0000000e+00 0.0000000e+00 1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00 0.0000000e+00]
[5.5263964e-33 1.0000000e+00 2.4369681e-30 0.0000000e+00 5.4157616e-27
7.3043492e-34 1.3921112e-31 8.2266358e-28 3.2682385e-35 0.0000000e+00]

В результате использования объединенного прогноза моделей c11(mae + sh) точно классифицируются 99.75%.
Если использовать объединенный прогноз:

c11(mae + sh + pss + bce + msle + mape +mse + lc) + c4(msle) + c6(lc),

то на тестовой выборке MNIST точно классифицируются 99.98%. Неверно классифицированные изображения показаны на рис. 24, который, конечно же, совпадают с изображениями рис. 22.

Неверно классифицированные изображения MNIST

Рис. 24. Изображения тестового набора данных MNIST, неверно классифицируемые объединенным прогнозом
модели c11(mae + sh + pss + bce + msle + mape +mse + lc) + c4(msle) + c6(lc)

На рис. 24 над каждой цифрой указаны истинное и предсказанные значения.
Индексы изображений в массиве тестовых образов:

1. i = 1014. На самом деле: 6. Прогноз: 5
2. i = 1901. На самом деле: 9. Прогноз: 4

Сохранение в бинарный файл прогнозов выбранной НС обеспечивает следующий код:

def writeToBin(file_name, y, txt):
    fp = open(file_name, 'wb')
    fp.write(y)
    fp.close()
    print(txt + file_name)
#
...
    y_pred = model.predict(X_test) # Предсказания модели НС на тестовой выборке
    writeToBin(pathToData + 'y_pred.bin', y_pred, 'Прогноз сохранен в файле ')

Чтение бинарных файлов с прогнозами различных НС, формирование объединенного прогноза и оценка его истинности обеспечивается следующим кодом:

import numpy as np
import matplotlib.pyplot as plt
#
pathToErrData = 'G:/AM/НС/y_pred/'
dataSetNum = 1 # 1 - MNIST; 2 - EMNIST
showErrPics = True # True False
fn1 = 'y_pred_MNIST_mae_Adam_conv_11' + '.bin'
fn2 = 'y_pred_MNIST_sh_Adam_conv_11' + '.bin'
fn3 = 'y_pred_MNIST_pss_Adam_conv_11' + '.bin'
fn4 = 'y_pred_MNIST_bce_Adam_conv_11' + '.bin'
fn5 = 'y_pred_MNIST_msle_Adam_conv_11' + '.bin'
fn6 = 'y_pred_MNIST_mape_Adam_conv_11' + '.bin'
fn7 = 'y_pred_MNIST_mse_Adam_conv_11' + '.bin'
fn8 = 'y_pred_MNIST_lc_Adam_conv_11' + '.bin'
fn9 = 'y_pred_MNIST_msle_conv_4' + '.bin'
fn10 = 'y_pred_MNIST_lc_conv_6' + '.bin'
lst_fn = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10]
nf = len(lst_fn)
#
def load_y_pred(fn, num_classes):
    with open(pathToErrData + fn, 'rb') as f:
        y_p = np.fromfile(f, dtype = np.float32)
    n = int(len(y_p) / num_classes)
    y_pred = []
    m = -1
    for i in range(n):
        one_y = []
        for j in range(num_classes):
            m += 1
            one_y.append(y_p[m])
        y_pred.append(one_y)
    return np.asarray(y_pred)
def make_y_pred(n_test, y_test_0, y_pred, fn, num_classes):
    y2 = load_y_pred(fn, num_classes)
    print('Прогнозы из ' + fn + ':')
    print(y2[0:3])
    cl = []
    for m in y_pred:
        cl.append(np.argmax(m))
    cl2 = []
    for m in y2:
        cl2.append(np.argmax(m))
    for k in range(n_test):
        c = y_test_0[k]
        if cl[k] != c and cl2[k] == c: y_pred[k] = y2[k]
    return y_pred
def makeNames(imgType):
    names = []
    if imgType == 'MNIST':
        for i in range(10): names.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
    elif imgType == 'EMNIST':
        for i in range(26): names.append(chr(65 + i)) # ['A', 'B', 'C', ..., 'Z']
    return names
def load_test(pathToData):
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    X_test = np.asarray(data2)
    y_test = np.asarray(labels2)
    return X_test, y_test, len(y_test)
#
# Путь к тестовым данным
p0 = 'G:/AM/НС/'
if dataSetNum == 1:
    pathToData = p0 + 'mnist/'; imgType = 'MNIST'
elif dataSetNum == 2:
    pathToData = p0 + 'emnist/'; imgType = 'EMNIST'
else: print('Плохой номер набора данных'); sys.exit()
#
X_test, y_test_0, n_test = load_test(pathToData)
#
if dataSetNum == 1:
    num_classes = 10
else:
    num_classes = 26
y_pred = load_y_pred(fn1, num_classes)
print('Прогнозы из ' + fn1 + ':')
print(y_pred[0:3])
for m in range(1, nf):
    y_pred = make_y_pred(n_test, y_test_0, y_pred, lst_fn[m], num_classes)
print('Итоговые прогнозы:')
print(y_pred[0:3])
#
classes = []
for m in y_pred:
    classes.append(np.argmax(m))
nClassified = np.sum(classes == y_test_0)
nNotClassified = n_test - nClassified
acc = 100.0 * nClassified / n_test
print("Число ошибочно классифицированных образов: " + str(nNotClassified))
print("Точность прогнозирования: " + str(acc) + '%')
if showErrPics:
    if imgType == 'MNIST':
        X_test = X_test.reshape(n_test, 28, 28, 1)
    elif imgType == 'EMNIST':
        X_test = X_test.reshape(n_test, 28, 28, 1).transpose(0,2,1,3)
        y_test_0 -= 1
    names = makeNames(imgType)
    n = 0
    for i in range(n_test):
        s_true = names[y_test_0[i]]
        s_pred = names[classes[i]]
        if s_true != s_pred:
            n += 1
            if (n > nNotClassified): break
            str_i = str(i);
            print(str(n) + '. i = ' + str_i + '. На самом деле: ' + s_true + '. Прогноз: ' + s_pred)
            if n < 28:
                plt.subplot(3, 9, n)
                plt.imshow(X_test[i].reshape(28, 28), cmap = plt.get_cmap('gray'))
                plt.title(s_true + '/' + s_pred)
                plt.axis('off')
    plt.subplots_adjust(hspace = 0.5)
    plt.show()

Списки эффективных функций потерь

Списки эффективных функций потерь по критерию val_acc на наборах данных MNIST, EMNIST и CIFAR-10 построены по историям обучения нейронных сетей и приведены в табл. 21.

Таблица 21. Списки эффективных функций потерь по критерию val_acc на наборах данных MNIST, EMNIST и CIFAR-10

Модель НССписок эффективных ФП
MNIST
с1['msle', 'mk', 'pss', 'bce', 'sh']
c4['ck']
c5['h', 'bce', 'lc', 'msle', 'mse']
c6['kld', 'cce', 'ck']
c7['pss', 'msle', 'cp']
c8['cp']
с9['bce']
c10['mse', 'kld', 'pss']
с11['kld', 'mae']
с12['cce', 'scce', 'cp']
2с12['ck', 'msle', 'cp', 'bce', 'kld', 'mse']
2с12bn['mape', 'sh', 'h']
3с12['cce', 'ck', 'kld', 'cp', 'pss', 'h', 'scce', 'msle']
с13['cce', 'mse']
c14['cp', 'kld', 'ck', 'msle', 'sh', 'bce', 'cce']
с15['cp', 'pss']
с16['kld', 'scce']
c17['scce']
c18['msle', 'scce', 'kld', 'mk', 'cp', 'cce']
с34bn['msle', 'pss', 'sh']
c36['mape']
EMNIST
c1['lc', 'msle', 'ck']
c02['bce']
c3['mse']
c4['mse', 'msle']
c5['mse', 'msle', 'lc']
c7['sh', 'cp', 'msle']
c9['ch']
c10['msle']
c11['mse']
c12['msle', 'mse', 'lc', 'scce']
2c12['sh', 'cce', 'msle']
3c12['lc', 'sh']
c13['msle', 'sh']
c14['mse', 'lc']
c15['cp', 'lc', 'scce', 'mse', 'kld', 'mk']
c17['mse', 'ck', 'msle']
c19['lc', 'msle']
c20['sh', 'mse']
2c20['sh', 'lc']
3c20['cp', 'msle', 'lc', 'pss']
c21['msle', 'mse', 'pss', 'mk', 'cp']
c24['msle', 'ck']
2c24['mk', 'msle', 'sh']
c25['lc']
c26['mse', 'scce', 'msle']
c27['lc']
2c27['mse']
c28['mse']
2c28['lc']
2c28_2['lc', 'pss', 'msle']
2c28bn['msle', 'h', 'scce', 'mae']
c34bn['msle', 'cp', 'mse', 'bce', 'lc', 'h', 'ch']
c36['scce']
CIFAR-10
c1['cce', 'scce', 'msle']
c33['bce', 'msle']
c4['scce']
с5['msle', 'bce', 'ck']
c7['ck', 'mk']
c13['cp', 'bce']
c14['cce']
2c14['cp', 'mk']
3c14['cp']
c15['bce', 'pss', 'cp']
c16['cce', 'pss', 'kld', 'bce', 'ck']
c17['scce', 'kld']
c18['scce', 'kld', 'pss', 'mk']
c34bn['bce', 'ch']
c36['pss', 'bce']

Приведенные в табл. 21 данные позволяют заключить следующее:

  1. Нельзя переносить опыт обучения одной НС на другую, то есть нельзя утверждать, что функции потерь, эффективные для одной нейронной сети, будут эффективны для другой. Например, на MNIST для НС c1 эффективны следующие функции потерь: 'msle', 'mk', 'pss', 'bce', 'sh', а для НС c4 – только функция потерь 'ck'. Хотя бывают и совпадения. Например, на EMNIST НС 2c27 и c28 лучшим образом обучаются с функцией потерь 'mse'.
  2. Разные функции потерь появляются в списках эффективных ФП с разной частотой.
  3. При выборе функции потерь можно опираться на частотные диаграммы эффективности ФП, проверяя прежде ФП, наиболее часто появляющиеся в списках эффективных ФП. (Построенные по табл. 21 диаграммы частоты появления функций потерь в списках эффективных ФП показаны на рис. 25.)
  4. При отсутствии частотных диаграмм эффективности ФП, например при работе с новым набором данных, поиск эффективных ФП следует вести, выполняя полный перебор доступных ФП.
MNIST
EMNIST
CIFAR-10

Рис. 25. Частотные диаграммы эффективности функций потерь по критерию val_acc на наборах данных MNIST, EMNIST и CIFAR-10

Построение диаграмм обеспечивает следующий код.

import numpy as np
import matplotlib.pyplot as plt
dataSetNum = 3
if dataSetNum == 1:
    ttl = 'MNIST'
    lst = [['msle', 'mk', 'pss', 'bce', 'sh'], ['ck'],
         ['h', 'bce', 'lc', 'msle', 'mse'],
         ['kld', 'cce', 'ck'], ['pss', 'msle', 'cp'], ['cp'], ['bce'],
         ['mse', 'kld', 'pss'], ['kld', 'mae'], ['cce', 'scce', 'cp'],
         ['ck', 'msle', 'cp', 'bce', 'kld', 'mse'], ['mape', 'sh', 'h'],
         ['cce', 'ck', 'kld', 'cp', 'pss', 'h', 'scce', 'msle'],
         ['cce', 'mse'], ['cp', 'kld', 'ck', 'msle', 'sh', 'bce', 'cce'],
         ['cp', 'pss'], ['kld', 'scce'], ['scce'],
         ['msle', 'scce', 'kld', 'mk', 'cp', 'cce'], ['msle', 'pss', 'sh'],
         ['mape']]
elif dataSetNum == 2:
    ttl = 'EMNIST'
    lst = [['lc', 'msle', 'ck'], ['bce'], ['mse'], ['mse', 'msle'],
         ['mse', 'msle', 'lc'], ['sh', 'cp', 'msle'], ['ch'], ['msle'],
         ['mse'], ['msle', 'mse', 'lc', 'scce'], ['sh', 'cce', 'msle'],
         ['lc', 'sh'], ['msle', 'sh'], ['mse', 'lc'],
         ['cp', 'lc', 'scce', 'mse', 'kld', 'mk'], ['mse', 'ck', 'msle'],
         ['lc', 'msle'], ['sh', 'mse'], ['sh', 'lc'],
         ['cp', 'msle', 'lc', 'pss'], ['msle', 'mse', 'pss', 'mk', 'cp'],
         ['msle', 'ck'], ['mk', 'msle', 'sh'], ['lc'],
         ['mse', 'scce', 'msle'], ['lc'], ['mse'], ['mse'], ['lc'],
         ['lc', 'pss', 'msle'], ['msle', 'h', 'scce', 'mae'],
         ['msle', 'cp', 'mse', 'bce', 'lc', 'h', 'ch'], ['scce']]
else:
    ttl = 'CIFAR10'
    lst = [['cce', 'scce', 'msle'], ['bce', 'msle'], ['scce'],
         ['msle', 'bce', 'ck'], ['ck', 'mk'], ['cp', 'bce'],
         ['cce'], ['cp', 'mk'], ['cp'], ['bce', 'pss', 'cp'],
         ['cce', 'pss', 'kld', 'bce', 'ck'], ['scce', 'kld'],
         ['scce', 'kld', 'pss', 'mk'], ['bce', 'ch'], ['pss', 'bce']]
accF, accV = [], []
for e in lst:
    for ls in e:
        if ls in accF:
            ind = accF.index(ls)
            accV[ind] += 1
        else:
            accF.append(ls)
            accV.append(1)
n = len(accF)
lst = []
for k in range(n):
    lst.append([accF[k], accV[k]])
lst.sort(key = lambda r:(r[1]))
accF, accV = [], []
for k in range(n):
    fun = lst[k][0]
    v = lst[k][1]
    accV.append(v)
    accF.append(fun)
ind = np.arange(n)
width = 0.15 # Ширина столбца диаграммы
plt.figure(figsize = (6, 3.9))
if dataSetNum == 2: plt.ylim(0, 18)
plt.bar(ind, accV, width)
plt.xlabel('Функция потерь')
plt.ylabel('Частота')
plt.title(ttl)
plt.xticks(ind, accF)
plt.show()

Заключение

На языке Python с использование библиотеки Keras разработаны программные средства, обеспечивающие создание моделей НС по их строковому описанию, обучение и тестирование обученных моделей. Разработанные программы применены для обучения описанных в табл. 4 моделей НС.
Результаты (истории) обучения сохранены в текстовые файлы, содержащие для каждой эпохи обучения точность классификации и потери на обучающих и проверочных данных.
Разработаны программы анализа историй обучения, позволяющие выделять лучшие решения и эффективные функции потерь по критериям обучения corr_img, val_acc и acc, а также находить в каждом решении наибольшее число эпох между двумя последовательными обновлениями максимума критерия обучения и разницу между двумя последними обновлениями максимума критерия обучения.
Предусмотрены средства для графического отображения результатов обучения и их анализа.
Результаты обучения и разработанные программы приведены в приложениях. Результаты анализа историй обучения – в разд. Оценка результатов обучения, Погрешность оценки результатов обучения и Обработка историй обучения.
Созданы программы, находящие пересечение списков неверно классифицированных изображений разными моделями НС и отображающие эти пересечения в виде рисунков. Аналогичный результат можно получить, объединяя прогнозы разных моделей НС.
Обработка историй обучения позволила, в частности, выделить для каждой НС эффективные функции потерь. Нередко для НС эффективными являются несколько функций потерь. При этом в общем случае нельзя переносить опыт обучения одной НС на другую: поиск функций потерь, эффективных для проектируемой НС, выполняется в результате перебора всех доступных функций потерь.
В случае MNIST и EMNIST загрузка наборов данных выполняется из бинарных файлов. Путь к этим файлам содержит костанта pathToData.
В случае CIFAR-10 в папке, имя которой хранит константа pathToData, следует разместить папку cifar-10-batches-py с данными CIFAR-10.
Можно скачать все истории обучения. Для их обработки используются программа, приведенная в прил. 5.
Разработанные средства могут быть использованы для классификации и обработки результатов классификации изображений разных видов, в частности, MNIST, EMNIST CIFAR-10.

Источники

1. The MNIST database of handwritten digits. [Электронный ресурс] URL: http://yann.lecun.com/exdb/mnist/ (дата обращения: 01.01.2019).
2. The EMNIST dataset. [Электронный ресурс] URL: https://www.nist.gov/itl/iad/image-group/emnist-dataset (дата обращения: 01.01.2019).
3. The CIFAR-10 dataset. [Электронный ресурс] URL: http://www.cs.toronto.edu/~kriz/cifar.html (дата обращения: 01.01.2019).
4. Функции потерь библиотеки Keras. [Электронный ресурс] URL: http://100byte.ru/python/loss/loss.html (дата обращения: 01.01.2019).
5. He K., Zhang X., Ren S., Sun J. Deep Residual Learning for Image Recognition. [Электронный ресурс] URL: https://arxiv.org/pdf/1512.03385.pdf (дата обращения: 01.01.2019).
6. Keras documentation. Merge Layers. [Электронный ресурс] URL: https://keras.io/layers/merge/ (дата обращения: 01.01.2019).
7. Classification datasets results. [Электронный ресурс] URL: http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results (дата обращения: 01.01.2019).
8. Liu Y., Li H., Wang X. Rethinking feature discrimination and polymerization for large-scale recognition. [Электронный ресурс] URL: https://arxiv.org/pdf/1710.00870.pdf (дата обращения: 01.01.2019).
9. Gregory Cohen, Saeed Afshar, Jonathan Tapson, and Andre van Schaik. EMNIST: an extension of MNIST to handwritten letters. [Электронный ресурс] URL: https://arxiv.org/pdf/1702.05373v1.pdf (дата обращения: 01.01.2019).
10. Keras documentation. How can I obtain reproducible results using Keras during development? [Электронный ресурс] URL: https://keras.io/getting-started/faq/#how-can-i-obtain-reproducible-results-using-keras-during-development (дата обращения: 01.01.2019).

Приложение 1. Результаты обучения нейронных сетей

Результаты обучения перечисленных в табл. 4 моделей НС, приведены в табл. П1.

Таблица П1. Результаты обучения моделей НС (решения выбираются по критерию val_acc)

Функция потерьЧисло эпох
обучения
Эпоха
получения
результата
Точность, %
тест или
тест / обучение
Расшифровка обозначений в заголовке одного цикла обучения
Набор данных / Тип НС / Число параметров
Код = Обозначение модели / [avg] / [(min – max)] / [avg_acc] / [среднее число эпох]
avg, min, max (для val_acc)– средняя, минимальная, максимальная точность в серии
(указанные в квадратных скобках показатели могут быть опущены)
MNIST / МП / 693'546
m2 = DR800-DR80-DL16 / 98.34 (98.19 – 98.49)
m7 = 0.25-DR800-DR80-DL16 (план)
m8 = 0.25-DR800-0.25-DR80-DL16 (план)
m9 = 0.25-DR800-0.25-DR80-0.2-DL16 (план)
1MSE1205598.41
2MAE1205298.19
3MAPE1206098.21
4MSLE1203698.38
5SH1204998.33
6H1205698.22
7CH1204298.26
8LC1202998.35
9CCE1204298.40
10SCCE1203898.47
11BCE908698.49
12KLD1204298.36
13PSS1204798.43
14CP1204898.32
15CCE_KLD1205898.38
16MSE_KLD1203898.45
17myLoss1205498.42
MNIST / МП / 694'820
m1 = DR800-DR80-DL30 / 98.31 (98.19 – 98.56)
m4 = 0.25-DR800-DR80-DL30 / 98.675 (98.45 – 98.80)
m5 = 0.25-DR800-0.25-DR80-DL30 / 98.71 (98.42 – 98.82)
m6 = 0.25-DR800-0.25-DR80-0.2-DL30 / 98.695 (98.37 – 98.87)
2m6: 1'389'640 (план)
1MSE12056
33
81
51
98.34
98.74
98.73
98.76
2MAE12057
73
58
60
98.27
98.53
98.42
98.47
3MAPE12042
64
64
45
98.19
98.55
98.48
98.37
4MSLE12054
61
65
37
98.36
98.67
98.75
98.80
5SH12049
41
94
81
98.29
98.59
98.80
98.79
6H12060
46
55
79
98.23
98.45
98.54
98.45
7CH12047
59
53
56
98.25
98.60
98.51
98.48
8LC12035
76
73
68
98.39
98.66
98.75
98.73
9CCE12055
45
87
54
98.48
98.71
98.81
98.83
10SCCE12066
46
69
56
98.43
98.74
98.82
98.76
11BCE12044
52
51
57
98.56
98.77
98.75
98.73
12KLD12050
52
88
80
98.47
98.80
98.79
98.87
13PSS120107
50
55
80
98.50
98.78
98.75
98.81
14CP12046
34
55
69
98.29
98.70
98.73
98.69
15CCE_KLD12040
71
94
52
98.54
98.75
98.80
98.74
16MSE_KLD 12051
34
83
69
98.41
98.73
98.81
98.83
17myLoss12053
34
52
52
98.48
98.71
98.81
98.70
MNIST / МП / 696'822
m3 = DR800-DR80-DL52 / 98.425 / 98.82 (средние val_acc / acc)
m10 = 0.25-DR800-DR80-DL52 / (98.66 / 99.76)
m11 = 0.25-DR800-0.25-DR80-DL52 / (98.71 / 99.70)
m12 = 0.25-DR800-0.25-DR80-0.2-DL52 / (98.60 / 99.68)
1MSE120 60
49
79
59
98.43 / 99.83
98.64 / 99.82
98.85 / 99.89
98.79 / 99.81
2MAE120 53
32
39
48
98.19 / 99.47
98.44 / 99.27
98.36 / 99.06
98.22 / 99.00
3MAPE120 47
57
35
43
98.22 / 99.44
98.53 / 99.43
98.36 / 98.90
98.24 / 98.95
4MSLE120 60
51
95
44
98.41 / 99.83
98.74 / 99.76
98.83 / 99.89
98.64 / 99.72
5SH120 52
48
57
76
98.42 / 99.75
98.72 / 99.72
98.78 / 99.75
98.74 / 99.80
6H120 53
60
51
47
98.44 / 99.60
98.34 / 99.42
98.38 / 99.00
98.23 / 99.09
7CH120 47
29
38
41
98.17 / 99.43
98.55 / 99.29
98.31 / 99.08
98.30 / 99.02
8LC120 34
73
54
46
98.39 / 99.83
98.72 / 99.88
98.70 / 99.78
98.78 / 99.71
9CCE120 53
23
74
87
98.45 / 100.0
98.73 / 99.82
98.78 / 99.98
98.78 / 99.997
10SCCE120 56
59
46
66
98.48 / 100.0
98.73 / 99.98
98.83 / 99.97
98.84 / 99.98
11BCE120 56
102
73
90
98.55 / 99.998
98.81 / 99.997
98.86 / 99.98
98.00 / 99.98
12KLD120 54
34
46
59
98.56 / 99.998
98.73 / 99.94
98.84 / 99.94
98.77 / 99.97
13PSS120 31
42
83
52
98.58 / 100.0
98.73 / 99.95
98.91 / 99.99
98.77 / 99.96
14CP120 38
66
59
71
98.35 / 99.83
98.73 / 99.85
98.71 / 99.75
98.72 / 99.73
15CCE_KLD120 58
43
73
93
98.64 / 99.998
98.71 / 99.95
98.88 / 99.98
98.79 / 99.997
16MSE_KLD120 44
51
66
48
98.47 / 99.93
98.76 / 99.96
98.83 / 99.99
98.82 / 99.94
17myLoss120 58
51
64
52
98.48 / 99.92
98.65 / 99.93
98.80 / 99.93
98.78 / 99.92
MNIST / СНС / 158'080
c5 = CR20-P-CR30-P-F-0.2-DR100 / 99.41 (99.37 – 99.48) / 60
1MSE1205799.43
2MAE1206499.41
3MAPE1206299.37
4MSLE1206199.43
5SH1207399.40
6H1209899.45
7CH1207199.42
8LC1206899.44
9CCE1202399.40
10SCCE1208999.38
11BCE12048/5799.44/99.48 / -/99.997
12KLD1204899.40
13PSS1206799.40
14CP1204499.37
15CCE_KLD1206499.41
16MSE_KLD1205399.42
17myLoss1201699.41
MNIST / СНС / 272'778
c36 = ResNet20v1 / (98.33 – 99.33)
1MSE12012098.94
2MAE12012098.36
3MAPE12012099.33
4MSLE12012098.72
5SH12011198.66
6H1206598.37
7CH1207798.85
8LC1207897.98
9CCE1204399.12
10SCCE1208999.14
11BCE1205998.99
12KLD1207499.05
13PSS12010998.71
14CP12012098.77
15CCE_KLD1209599.20
16MSE_KLD12011199.13
MNIST / СНС
2c5: 316'160 (c5 = CR20-P-CR30-P-F-0.2-DR100)
8LC605199.51
MNIST / СНС
3c5: 474'240 (c5 = CR20-P-CR30-P-F-0.2-DR100)
8LC907899.50
9CCE125599.46
11BCE905299.47
13PSS1204099.52
MNIST / СНС / 226'395
c7 = CR10-P-CR15-P-F-0.2-DR300 / 99.40 (99.31 – 99.45) / 66
1MSE1204299.38
2MAE1207099.42
3MAPE1207799.31
4MSLE1206599.43
5SH1206499.42
6H1207099.40
7CH1207399.38
8LC1207099.41
9CCE1205599.34
10SCCE1206199.42
11BCE12024/7399.33/99.44 / -/99.996667
12KLD1206199.37
13PSS1205299.45
14CP1207199.43
15CCE_KLD1206499.38
16MSE_KLD1208099.42
17myLoss1207099.44
MNIST / СНС / 232'725
c8 = CR10-P-CR15-P-F-0.25-DR300-DL30 / 99.33 (99.26 – 99.41) / 55
1MSE604599.35
2MAE8069/7499.31/99.32
3MAPE805699.29
4MSLE804599.34
5SH803999.36
6H805099.26
7CH806599.35
8LC802499.37
9CCE807299.37
10SCCE807899.32
11BCE8075/5099.34/99.32
12KLD806999.34
13PSS806199.29
14CP807299.41
15CCE_KLD801799.32
16MSE_KLD807299.33
17myLoss805099.34
MNIST / СНС / 238'945
c29 = CR10-P-CR15-P-F-0.3-DR300-0.25-DL50-0.2
2c29: 477'890
3c29: 716'835
8LC12057
73
52
99.51
99.51
99.51
9CCE12044
66
61
99.44
99.50
99.50
10SCCE12071
84
-
99.45
99.49
-
13PSS12079
64
75
99.46
99.47
99.49
MNIST / СНС / 898'580
c6 = CR20-P-CR30-P-F-DR600 / 99.40 (99.34 – 99.45) / 74
1MSE12055/4199.44/99.36
2MAE12086/3199.40/99.36
3MAPE12089/5099.40/99.40
4MSLE120120/3799.41/99.35
5SH12069/6599.40/99.31
6H12085/5199.34/99.36
7CH12085/4999.34/99.27
8LC12070/1799.45/99.36
9CCE12069/5199.40/99.44
10SCCE12089/2699.40/99.37
11BCE12021/55/3299.36/99.45/99.41 / -/99.9983
12KLD12038/3999.37/99.45
13PSS12032/5099.41/99.39
14CP12078/2899.39/99.36
15CCE_KLD120102/10899.43/99.44
16MSE_KLD120119/2799.40/99.39
17myLoss1202099.34
MNIST / СНС / 902'356
c9 = CR20-P-CR30-P-F-DR600-DL16 / 99.41 (99.32 – 99.55) / 99.92 (99.76 – 99.998) / 71
c11 = CR20-P-CR30-P-F-0.25-DR600-DL16 / 99.47 (99.33 – 99.53) / 99.925 (99.79 – 99.997) / 61
c12 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 99.49 (99.41 – 99.55) / 99.92 (99.61 – 99.97) / 69
2c12: 1'804'712 / 99.53 (99.45 – 99.61) / 99.92 (99.79 – 99.98) / 52
3c12: 2'707'068 / 99.54 (99.46 – 99.60) / 99.92 (99.82 – 99.96) / 55
1MSE120 100/35
94
66
42
46
99.39/99.38 / 99.94/-
99.48 / 99.97
99.50 / 99.95
99.56 / 99.95
99.53 / 99.95
2MAE120 59/64
54
109
45
70
99.37/99.29 / 99.82/-
99.51 / 99.80
99.42 / 99.79
99.45 / 99.80
99.49 / 99.85
3MAPE120 48/35
110
36
47
58
99.41/99.26 / 99.80/-
99.48 / 99.87
99.49 / 99.61
99.53 / 99.79
99.52 / 99.82
4MSLE120 94/28
103
65
46
38
99.40/99.36 / 99.91/-
99.49 / 99.96
99.49 / 99.96
99.57 / 99.96
99.55 / 99.94
5SH120 79/19
76
81
45
48
99.39/99.36 / 99.95/-
99.50 / 99.945
99.52 / 99.96
99.53 / 99.945
99.54 / 99.96
6H120 37/30
61
58
49
68
99.33/99.30 / 99.76/-
99.43 / 99.82
99.41 / 99.79
99.50 / 99.80
99.56 / 99.85
7CH120 100/20
69/75
105
46
77
99.32/99.30 / 99.87/-
99.42/99.33 / 99.79/99.80
99.41 / 99.85
99.47 / 99.81
99.51 / 99.86
8LC120 21/31
60/39
55
64
40/70
99.44/99.30 / 99.88/-
99.52/99.48 / 99.93/-
99.49 / 99.93
99.53 / 99.96
99.46/99.54 / 99.93/-
9CCE120 51/34
85/51
110
56
84
99.48/99.28 / 99.998/-
99.48/99.48 / 99.99/-
99.55 / 99.99
99.54 / 99.98
99.57 / 99.96
10SCCE120 101/31
86/31
84
81/34
60
99.49/99.45 / 99.998/-
99.52/99.47 / 99.997/-
99.55 / 99.997
99.61/99.55 / 99.975/-
99.55 / 99.96
11BCE120 38/88/65
15/61/46
71/12
79
56/31
99.55/99.49/99.44 / 99.9983/99.99
99.46/99.50/99.45 / -/99.99/99.98
99.49/99.41 / -/99.91
99.56 / -
99.54/99.48 / -/99.95
12KLD120 41/118
116
40
41
52/35
99.43/99.44 / 99.998/-
99.53 / 99.99
99.51 / 99.99
99.56 / 99.96
99.60/99.57 / 99.95/-
13PSS120 105/33
71
44
39
48
99.44/99.44 / 99.998/-
99.50 / 99.995
99.49 / 99.99
99.45 / 99.93
99.56 / 99.93
14CP120 39
25
81
62
42
99.41 / 99.93
99.48 / 99.85
99.55 / 99.95
99.56 / 99.97
99.56 / 99.95
15CCE_KLD120 90/17
80/46
84
42
39
99.48/99.30 / 99.998/-
99.52/99.47 / 99.997/-
99.51 / 99.995
99.58 / 99.94
99.57 / 99.94
16MSE_KLD120 117/27
94
95
43
46
99.48/94.41 / 99.998/-
99.50 / 99.99
99.50 / 99.99
99.51 / 99.96
99.54 / 99.95
17myLoss 120
120
80
120
120
29
31
62
58
55
99.36 / 99.90
99.49 / 99.90
99.54 / 99.95
99.59 / 99.96
99.53 / 99.91
MNIST / СНС
2c12: 1'804'712 / (99.45 / 99.46 – 99.60 / 99.59)
(c12 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL16)
Два повторных обучения (второе и третье). В скобках результаты первого обучения
1MSE90 / 120 (120)43 / 78 (42)99.60 / 99.57 (99.56)
2MAE90 / 120 (120)53 / 64 (45)99.50 / 99.47 (99.45)
3MAPE90 / 120 (120)51 / 35 (47)99.53 / 99.52 (99.53)
4MSLE90 / 120 (120)74 / 77 (46)99.57 / 99.52 (99.57)
5SH90 / 120 (120)42 / 99 (45)99.51 / 99.51 (99.53)
6H90 / 120 (120)55 / 88 (49)99.50 / 99.51 (99.50)
7CH90 / 120 (120)40 / 62 (46)99.45 / 99.46 (99.47)
8LC90 / 120 (120)51 / 117 (64)99.55 / 99.58 (99.53)
9CCE90 / 120 (120)31 / 117 (56)99.55 / 99.55 (99.54)
10SCCE90 / 120 (120)33 / 58 (81)99.57 / 99.56 (99.55)
11BCE90 / 120 (120)113 /44 (52)99.55 / 99.52 (99.57)
12KLD90 / 120 (120)45 / 36 (41)99.53 / 99.56 (99.56)
13PSS90 / 120 (120)53 / 68 (39)99.52 / 99.55 (99.45)
14CP90 / 120 (120)41 / 107 (62)99.51 / 99.52 (99.56)
15CCE_KLD90 / 120 (120) 64 / 98 (42) 99.53 / 99.56 (99.58)
16MSE_KLD90 / 120 (120) 37 / 68 (43) 99.53 / 99.59 (99.51)
MNIST / СНС
2c12bn: 1'807'376 / (99.55 – 99.64)
(c12bn = C20-BN-R-P-C30-BN-R-P-F-0.25-D600-BN-R-0.2-D16-BN-L)
1MSE12011099.58
2MAE1208299.60
3MAPE12011199.64
4MSLE12011899.57
5SH1203399.63
6H12011399.62
7CH1208699.60
8LC12010799.61
9CCE1207299.58
10SCCE12010099.59
11BCE1205499.60
12KLD1209999.57
13PSS1209799.58
14CP12010399.58
15CCE_KLD1208399.56
16MSE_KLD12011799.55
MNIST / СНС
2c12: 1'804'712 (c12 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL16)
Продолжаем обучать с SCCE после CCE_KLD с результатом 99.58
18CCE_KLD+SCCE12042+299.62 / 99.95
MNIST / СНС / 910'910
c4 = CR20-P-CR30-P-F-DR600-DL30 / 99.40 (99.30 – 99.48) / 69
c13 = CR20-P-CR30-P-F-0.25-DR600-DL30 / 99.465 (99.41 – 99.54) / 79
c14 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / 99.47 (99.39 – 99.52) / 81
1MSE12058/40
113
58
99.39/99.35
99.53
99.49
2MAE12027/45
105
100
99.41/99.37
99.42
99.43
3MAPE12089/47
67
105
99.36/99.27
99.46
99.39
4MSLE120106/27
30
106
99.47/99.41
99.43
99.51
5SH12052/47
83
98
99.37/99.41
99.49
99.51
6H12080/47
54
90
99.36/99.36
99.41
99.40
7CH12081/43
105
79
99.30/99.26
99.41
99.36
8LC12099/15
50
51
99.43/99.35
99.48
99.49
9CCE12050/63
120
116
99.42/99.41
99.54
99.50
10SCCE12037/25
97
109
99.41/99.38
99.44
99.46
11BCE12029/60/27
23/100
23/82
99.35/99.45/99.45 / -/-/99.992
99.43/99.51 / -/99.998
99.51/99.52 / -/99.995
12KLD120101/27
50
106
99.41/99.39
99.49
99.51
13PSS12087/35
44
66
99.48/99.38
99.47
99.49
14CP12096/28
87
36
99.39/99.35
99.47
99.52
15CCE_KLD12085/44
120
98
99.39/99.44
99.48
99.51
16MSE_KLD12057/23
44
46
99.39/99.32
99.45
99.47
17myLoss12028
54
34
99.40
99.42
99.42
MNIST / СНС / 924'352
c10 = CR20-P-CR30-P-F-DR600-DL52 / 99.37 (99.28 – 99.43) / 65
1MSE12067/3199.35/99.40
2MAE120102/1799.35/99.23
3MAPE120119/4099.28/99.26
4MSLE12033/1699.32/99.30
5SH120107/3699.43/99.27
6H12097/3099.37/99.26
7CH12028/2899.33/99.29
8LC120111/1899.39/99.33
9CCE12039/8299.37/99.32
10SCCE12024/2599.37/99.31
11BCE12015/54/3399.32/99.37/99.34 / -/-/99.9983
12KLD12046/3799.42/99.39
13PSS12086/2399.43/99.39
14CP6035/1399.33/99.35
15CCE_KLD6026/6899.41/99.36
16MSE_KLD1203499.34
17myLoss1203099.38
MNIST / СНС / 924'352
c23 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2
1SCCE1201899.48
MNIST / СНС / 947'570
c24 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2
1SCCE12047/3799.47/99.51
MNIST / СНС / 1'728'074 (99.65 – 99.71)
c34 = C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5
1MSE1209899.65
2MAE1209199.68
3MAPE1206499.67
4MSLE120113 / 9199.71 / 99.68
5SH12011099.70
6H1207999.68
7CH1209799.67
8LC1209999.68
9CCE1209799.68
10SCCE1207299.68
11BCE1207899.66
12KLD12011599.66
13PSS1209199.70
14CP12011999.68
15CCE_KLD12010699.67
16MSE_KLD12010699.67
MNIST / СНС / 2'555'962
c15 = CR32-P-CR64-P-F-0.25-DR800-DL16 / 99.42 (99.34 – 99.53) / 38
c16 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL16 / 99.43 (99.36 – 99.49) / 33
1MSE6048
31
99.45
99.43
2MAE6030
24
99.45
99.40
3MAPE6039
32
99.36
99.36
4MSLE4032
23
99.46
99.48
5SH6055
41
99.42
99.43
6H6029
37
99.39
99.36
7CH6053
36
99.35
99.42
8LC6045/42
35
99.47/99.39
99.41
9CCE6034/12
21
99.41/99.37
99.47
10SCCE6050
61
99.38
99.51
11BCE120/8040/24
29/67/78
99.44/99.41 / -/99.97
99.46/99.47/99.46
12KLD6024
31
99.38
99.53
13PSS6047
38
99.51
99.41
14CP6042
24
99.53
99.44
15CCE_KLD60/12054
30/69
99.46
99.43/99.49
16MSE_KLD6032
40
99.42
99.49
17myLoss6015
27
99.39
99.43
MNIST / СНС / 2'567'316
c17 = CR32-P-CR64-P-F-0.25-DR800-DL30 (99.30 – 99.51)
c18 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL30 (99.31 – 99.48)
1MSE12011
29
99.40
99.39
2MAE12021/33
32
99.27/99.37
99.35
3MAPE12049
28
99.30
99.31
4MSLE12032
24
99.48
99.47
5SH12022
44
99.42
99.42
6H12023
23
99.33
99.41
7CH12024
39
99.35
99.35
8LC12028
18
99.43
99.44
9CCE12046
21
99.45
99.45
10SCCE12055
39
99.51
99.46
11BCE12030/36
21/50
99.42/99.41 / -/99.992
99.41/99.48 / -/99.995
12KLD12051
64
99.44
99.46
13PSS12012
40
99.41
99.44
14CP12023
27
99.39
99.46
15CCE_KLD12028
40
99.42
99.42
16MSE_KLD12037
44
99.47
99.46
MNIST / СНС / 3'276'724
c01 = CR32-P-CR64-P-F-DR1024-DL30 / 99.37 (99.22 – 99.47) / 50
c1 = CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 / (99.26 – 99.52)
1MSE12039
57
99.39
99.46
2MAE12033
33
99.28
99.26
3MAPE12051
44
99.25
99.38
4MSLE12039
45
99.33
99.52
5SH12035
51
99.37
99.50
6H12036
34
99.22
99.34
7CH120114
46
99.36
99.34
8LC12023
43
99.41
99.48
9CCE12084
23
99.39
99.49
10SCCE12052
60
99.47
99.48
11BCE12055
82/91
99.45
99.50/99.47 / -/100.0
12KLD12058
30
99.41
99.47
13PSS12037
30
99.37
99.51
14CP12048
48
99.36
99.46
15CCE_KLD12085
34
99.42
99.45
16MSE_KLD12042
58
99.38
99.51
17myLoss12024
-
99.35
-
MNIST / СНС / c02 = CR32-P-CR64-P-F-DR1024-DL16: 3'262'234
1KLD131399.39
2myLoss301699.40
MNIST / СНС / с3 = CR32-P-CR64-P-F-DR1024-DL52: 3'299'494
1myLoss301999.33
MNIST / РНС / r1 = L300-DL30: 404'140
1MSLE321599.08
2SCCE1309199.24
MNIST / РНС / r2 = L500-DL30: 1'073'340
1SCCE806299.33
EMNIST / СНС / 159'696
c5 = CR20-P-CR30-P-F-0.2-DR100 / 92.53 (78.10 – 94.22) / 45
1MSE1204494.22
2MAE1205988.99
3MAPE1205988.81
4MSLE1205394.20
5SH1204494.14
6H1205978.10
7CH1205793.88
8LC1204794.18
9CCE1204393.89
10SCCE1202993.96
11BCE1202593.92
12KLD1202994.12
13PSS1204794.04
14CP1203694.03
15CCE_KLD1203794.04
16MSE_KLD1204894.17
17myLoss1202594.10
EMNIST / СНС / 231'211
c7 = CR10-P-CR15-P-F-0.2-DR300 / 93.12 (87.01 – 94.26) / 35
c19 = CR10-P-CR15-P-F-0.25-DR300 / (87.11 – 94.42)
c20 = CR10-P-CR15-P-F-0.3-DR300 / (87.25 – 94.46)
2c20: 462'422 / (89.94 – 94.71)
3c20: 693'633 / (93.50 – 94.73)
1MSE 120 29
29
47
80
97
94.19
94.34
94.46
94.60
94.63
2MAE 120 47
30
90/70
100
120
87.23
89.69
90.17/87.41
89.94
94.25
3MAPE 120 60
30
89/95
79/109
114
91.12
90.67
94.04/93.91
91.39/94.34
94.34
4MSLE 120 23
27
26
44
64
94.22
94.38
94.43
94.65
94.72
5SH 120 53
29
45
86
73
94.26
94.13
94.49
94.59
94.61
6H 120 54
30
67/97
74/88
118
87.01
87.11
87.25/91.30
94.34/92.30
94.36
7CH 120 53
111
70
107
116
93.99
94.21
93.05
93.37
93.50
8LC 120 45
83
40
61
67
94.05
94.42
94.44
94.71
94.71
9CCE 120 46
61
29
116
69
93.99
94.35
94.30
94.62
94.66
10SCCE 120 30
29
43
81/120
83
94.16
94.25
94.43
94.55/94.67
94.61
11BCE 120 19
26
41
86
55
94.00
94.19
94.15
94.47
94.56
12KLD 120 11
40
35/18
109
67
94.08
94.24
94.35/94.23
94.60
94.60
13PSS 120 19
29
111
108
63
94.07
94.17
94.25
94.68
94.69
14CP 120 29
51
58
46
112
94.22
94.25
94.29
94.61
94.73
15CCE_KLD 120 26
18
29
101
98
94.13
94.26
94.32
94.54
94.61
16MSE_KLD 120 29
50
39
83
94
94.16
94.32
94.38
94.59
94.60
EMNIST / СНС / 231'211
c7 = CR10-P-CR15-P-F-0.2-DR300
c20 = CR10-P-CR15-P-F-0.3-DR300
17myLoss 120 20
50
94.03
94.36
EMNIST / СНС / 231'211
c21 = CR10-P-CR15-P-F-0.35-DR300 (90.15 – 94.49)
c22 = CR10-P-CR15-P-F-0.3-DR300-0.3 (план)
1MSE1203794.48
2MAE1208490.15
3MAPE1205891.16
4MSLE120
30
34
29
94.49
94.46
5SH12047/11393.89/94.43
6H1209494.03
7CH1208694.03
8LC1207094.44
9CCE1204794.38
10SCCE1205594.42
11BCE1204994.30
12KLD1206394.44
13PSS1206194.46
14CP1204694.45
15CCE_KLD1202994.39
16MSE_KLD 602694.46
EMNIST / СНС / 273'066
c36 = ResNet20v1 / (70.96 – 93.38)
1MSE18017090.95
2MAE12012085.49
3MAPE12012080.57
4MSLE12012090.87
5SH12011590.33
6H12011770.96
7CH12012090.18
8LC12012092.27
9CCE12012090.36
10SCCE1205993.38
11BCE12012082.29
12KLD12012089.68
13PSS12012090.93
14CP12012092.31
15CCE_KLD12012090.17
16MSE_KLD12012091.47
EMNIST / СНС / 902'628
c9 = CR20-P-CR30-P-F-DR600-DL16 / 93.08 (89.89 – 93.97) / 96.13 (92.11 – 98.08) / 23
c11 = CR20-P-CR30-P-F-0.25-DR600-DL16 / 93.495 (90.10 – 94.29) / 96.54 (91.83 – 98.38) / 31
c12 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL16 / 93.69 (90.06 – 94.53) / 96.67 (91.47 – 98.43) / 41
2c12: 1'805'256 / 94.12 (89.76 – 94.79) / 97.01 (91.23 – 98.53) / 61
3c12: 2'707'884 / 93.81 (87.97 – 94.83) / 96.76 (89.09 – 99.08) / 65
1MSE 120
120
60
120
120
17
27
60
89
63/37
93.84 / 97.32
94.29 / 97.50
94.52 / 98.28
94.73 / 98.46
94.70/94.68 / 98.35/-
2MAE 120
120
60
120
120
46
32/59
55
44
61
89.97 / 92.13
90.10/93.91 / 91.83/-
90.22 / 91.79
94.12 / 95.63
90.86 / 92.22
3MAPE 120
120
60
120
120
50
58
53
46
61
89.89 / 92.11
90.26 / 92.01
90.12 / 91.65
89.76 / 91.23
90.59 / 92.08
4MSLE 120
60/80
60
120
120
15
20/33
34
61
51/29
93.85 / 97.10
94.24/94.19 / 97.20/-
94.53 / 97.63
94.75 / 98.21
94.67/94.67 / 98.08/-
5SH 120
60/120
60
120
120
29
50/29
60
79
79/83
93.66 / 97.66
94.24/94.12 / 97.98/-
94.42 / 98.03
94.79 / 97.92
94.78/94.75 / 98.01/-
6H 120
60/80
60
120
120
37
49/62
33
59
53
89.97 / 92.32
90.44/90.32 / 92.14/-
90.06 / 91.47
90.73 / 92.19
87.97 / 89.09
7CH 120
60
60
120
120
37
44/35
45
62
69
93.97 / 96.39
94.08/94.06 / 96.12/-
94.25 / 95.88
94.35 / 96.10
94.45 / 96.21
8LC 120
60
60
120
120
26
34/34
44
80
53/30
93.76 / 97.77
94.17/94.21 / 97.74/-
94.51 / 97.89
94.72 / 98.53
94.83/94.52 / 98.03/-
9CCE 120
60/80
60/120
120
120
11
40/19
52/27
44
84
93.69 / 97.17
94.19/94.12 / 98.34/-
94.47/94.40 / 98.43
94.76 / 97.66
94.65 / 98.1
10SCCE 120
60
60
120
120
25
23
35
80
47
93.72 / 98.02
94.21 / 97.79
94.51 / 97.91
94.62 / 98.15
94.63 / 97.51
11BCE 120 22
16
29
52
46
93.21
93.92
94.42
94.59
94.66
12KLD 120
60/80
60
120
120
11
12/23
26
75
75
93.63 / 96.405
94.20/94.11 / 97.45/-
94.41 / 97.61
94.63 / 97.98
94.62 / 97.98
13PSS 120
30
60
120
120
11
23
30
30
71
93.77 / 96.69
94.16 / 97.80
94.38 / 97.93
94.70 / 97.33
94.69 / 97.89
14CP 120
60/80
60/120
120
120
27
17/39
47/81
72
50/61
93.63 / 97.39
93.98/94.16 / 96.60/-
94.48/94.44 / 97.62
94.63 / 98.33
94.68/94.71 / 98.31/-
15CCE_KLD 120
60
60
120
120
15
27
25
97/62
63/37
93.67 / 97.17
94.23 / 97.93
94.45 / 97.62
94.74/94.71 / 98.29/-
94.78/94.72 / 97.85/-
16MSE_KLD 120
60
60/120
120
120
11
14
52/44
34
69
93.82 / 93.82
94.15 / 96.98
94.41/94.45 / 98.50
94.65 / 97.53
94.64 / 98.01
17myLoss 120
60
60
120
120
18
43
20
55
82
93.81 / 97.735
94.27 / 98.38
94.44 / 97.26
94.72 / 98.08
94.56 / 99.08
EMNIST / СНС / 911'406
c4 = CR20-P-CR30-P-F-DR600-DL30 / 93.21 (86.18 – 93.98) / 24
c13 = CR20-P-CR30-P-F-0.25-DR600-DL30 / 93.69 (87.33 – 94.39) / 35
c14 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 (91.01 – 94.62)
2c14: 1'822'812 (план)
1MSE60 15
40
40
93.98
94.31
94.62
2MAE60 17/57
59
54
92.38/93.67
92.94
91.01
3MAPE60 27
48
60
86.16
87.33
93.78
4MSLE60 15
30
29
93.98
94.39
94.50
5SH60 12/15
20
31
93.82/93.84
94.38
94.53
6H60 27
52
45
92.50
92.97
92.88
7CH120
60
60
98/59
54
38
93.79/93.87
93.99
93.07
8LC60 15/16
29
30
93.94/93.81
94.30
94.62
9CCE60 12/15
27
30
93.72/93.77
94.17
94.52
10SCCE60 8/9
46
26
93.70/93.79
94.22
94.52
11BCE120 15/28
15
44
93.77/93.26
94.00
94.39
12KLD60 10/15
30
52
93.82/93.69
94.31
94.47
13PSS120/60
60
60
86/9
19
14
93.78/93.78
94.15
94.45
14CP60 19
29
34
93.83
94.31
94.43
15CCE_KLD60 13
13
15
93.66
94.20
94.47
16MSE_KLD60 8/10
29
30
93.85/93.73
94.28
94.50
17myLoss60 12
29
93.94
94.23
EMNIST / СНС / 911'406
c4 = CR20-P-CR30-P-F-DR600-DL30
c13 = CR20-P-CR30-P-F-0.25-DR600-DL30
2c13: 1'822'812
4MSLE6015
30
39
93.98
94.39
94.63
EMNIST / СНС / 925'200
c10 = CR20-P-CR30-P-F-DR600-DL52 / 93.38 (89.55 – 93.96) / 21
1MSE3015/1493.96/93.90
2MAE30/6017/3289.55/89.78
3MAPE6059/4493.41/89.70
4MSLE1201893.95
5SH3016/1993.88/93.80
6H1205690.83
7CH1203993.71
8LC1202093.86
9CCE3015/1393.78/93.66
10SCCE301093.78
11BCE301193.83
12KLD30/6010/1293.76/93.76
13PSS30/6016/893.86/93.76
14CP301893.90
15CCE_KLD301893.78
16MSE_KLD30/6011/1493.82/93.84
17myLoss301293.78
EMNIST / СНС / 925'200
c24 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2 (90.85 – 94.82)
2c24: 1'850'400 (90.23 – 94.91)
1MSE12039
79
94.66
94.83
2MAE12039
32
92.58
92.96
3MAPE12038
31
90.85
93.27
4MSLE12047
29
94.82
94.91
5SH12050
74
94.64
94.87
6H12027
38
92.64
90.23
7CH12035
41
93.97
93.12
8LC12043
46
94.70
94.84
9CCE12027
83
94.67
94.82
10SCCE12018
-
94.58
-
11BCE12022
50
94.50
94.83
12KLD12064
55
94.75
94.83
13PSS12040
89
94.62
94.85
14CP12072
66
94.65
94.83
15CCE_KLD12041/41
44
94.76/94.80
94.84
16MSE_KLD12015/45
86
94.75/94.76
94.92
EMNIST / СНС / 930'216
c25 = CR20-P-CR30-P-F-DR600-DL60 / (86.95 – 93.97) / (88.41 – 98.32)
c26 = CR20-P-CR30-P-F-0.3-DR600-DL60 / 93.87 (89.88 – 94.51) / 25 / (89.88 – 98.05)
c27 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL60 / (89.52 – 94.72)
c28 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2 / (90.64 – 94.85)
2c27: 1'860'432 / 93.87 (90.02 – 94.95) / 61
2c28: 1'860'432 / (91.40 – 95.03)
1MSE 120 34
34/30
40
29
42
53
93.77 / 98.32
94.37/94.49 / 97.85/-
94.64
94.85
94.95
94.91
2MAE 120 52
36
40
36
50
52
86.95 / 88.41
92.58 / 93.84
89.52
93.54
90.08
93.74
3MAPE 120 41
33
30
28
43
50
90.54 / 92.16
89.88 / 89.88
89.90
92.46
90.02
92.77
4MSLE 120 10
35/23
30
40
40
53
93.84 / 96.68
94.51/94.45 / 97.90/-
94.60
94.67
94.85
94.90
5SH 120 15
31/49
49
35
89
53
93.74 / 96.67
94.47/94.34 / 97.42/-
94.58
94.63
94.81
94.83
6H 120 30
29
40
48
36
28
86.97 / 88.56
92.63 / 93.87
92.53
90.64
90.23
91.40
7CH 120 31
43
51
35
53
32
93.71 / 95.74
93.85 / 95.50
93.90
93.64
94.21
94.19
8LC 120 11
25/18
46
50
50
61
93.91 / 96.56
94.50/94.40 / 97.29/-
94.72
94.73
94.88
95.03
9CCE 120 9
15/29
111
30
114
64
93.85 / 96.52
94.45/94.41 / 97.25/00/00
94.53
94.67
94.86
94.87
10SCCE 120 15
30/15
80
45
83
107
93.81 / 97.34
94.42/94.49 / 98.05/-
94.57
94.70
94.81
94.81
11BCE 120 15/20
11
23
28
74/58
88
93.91/93.41 / 97.52/-
94.17
94.43
94.59
94.81/94.80
94.80
12KLD 120 18/10
14/15
30
44
69
59
93.80/93.77 / 97.68/-
94.35/94.36 / 96.95/-
94.60
94.73
94.80
94.83
13PSS 120 15/10
11/15
75
61
80
37/104
93.97/93.83 / 97.51/-
94.39/94.33 / 96.78/-
94.64
94.63
94.79
94.73/94.89
14CP 120 17/18
30/25
37
46
47
74
93.86/93.75 / 97.01/-
94.39/94.34 / 97.49/-
94.62
94.59
94.86
94.88
15CCE_KLD 120 11
11/34
46
25
40
18/64
93.84 / 96.88
94.37/94.36 / 96.83/-
94.54
94.67
94.71
94.63/94.86
16MSE_KLD 120 10/10
17/20
40
30
72
63
93.85/93.72 / 96.68/-
94.44/94.42 / 97.24/-
94.57
94.66
94.81
94.83
EMNIST / СНС
2c28_2: 1'860'432 / (91.05 – 94.94) / (91.40 – 95.03)
(c28 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2)
Повторы; в скобках прежние результаты
1MSE90 / 120 (120)33 / 31 (53)94.86 / 94.92 (94.91)
2MAE90 / 120 (120)54 / 55 (52)91.05 / 93.56 (93.74)
3MAPE90 / 120 (120)32 / 45 (50)92.91 / 92.89 (92.77)
4MSLE90 / 120 (120)46 / 44 (53)94.91 / 94.95 (94.90)
5SH90 / 120 (120)64 / 40 (53)94.86 / 94.42 (94.83)
6H90 / 120 (120)70 / 25 (28)92.69 / 93/36 (91.40)
7CH90 / 120 (120)40 / 27 (32)94.14 / 94.03 (94.19)
8LC112 / 120 (120)94 / 42 (61)94.94 / 94.97 (95.03)
9CCE90 / 120 (120)74 / 83 (64)94.76 / 94.92 (94.87)
10SCCE90 / 120 (120)63 / 76 (107)94.84 / 94.92 (94.81)
11BCE90 / 120 (120)49 / 54 (88)94.72 / 94.89 (94.80)
12KLD90 / 120 (120)59 / 52 (59)94.81 / 94.89 (94.83)
13PSS90 / 120 (120)69 / 98 (104)94.92 / 94.93 (94.89)
14CP90 / 120 (120)67 / 83 (74)94.82 / 94.94 (94.88)
15CCE_KLD90 / 120 (120)55 / 105 (64)94.87 / 94.88 (94.86)
16MSE_KLD90 / 120 (120)64 / 50 (63)94.85 / 94.84 (94.83)
EMNIST / СНС
2c28bn: 1'863'272 / (95.07 – 95.25)
(c28bn = C20-BN-R-P-C30-BN-R-P-F-0.3-D600-BN-R-0.2-D60-BN-L-0.2)
1MSE1202795.11
2MAE12011995.21
3MAPE12011895.13
4MSLE1205695.25
5SH1205595.17
6H12010595.24
7CH1209795.19
8LC1207195.20
9CCE1204195.07
10SCCE1204195.23
11BCE1205395.14
12KLD1209095.1
13PSS1205595.15
14CP1208195.11
15CCE_KLD1208895.12
16MSE_KLD1206995.19
EMNIST / СНС / 930'216
2c25: 1'860'432
1H1204690.18
EMNIST / СНС / 949'026
c23 = CR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2 (продолжить)
1MSE1200000.00
2MAE1202290.36
3MAPE1203393.28
4MSLE30/12023/6194.68/94.66
5SH1205894.58
6H1203190.23
7CH1203693.74
8LC30/12030/5994.67/94.64
9CCE1200000.00
10SCCE1200000.00
11BCE1200000.00
12KLD1200000.00
13PSS1204294.64
14CP1200000.00
15CCE_KLD1204794.65
16MSE_KLD1200000.00
EMNIST / СНС / 1'736'282 (95.30 – 95.43)
c34 = C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5
1MSE1203995.42
2MAE12010395.38
3MAPE1208095.30
4MSLE1204495.43
5SH1207195.38
6H1208995.40
7CH1206995.39
8LC1203095.41
9CCE1202995.37
10SCCE1203095.35
11BCE1202795.41
12KLD1204995.35
13PSS1203695.31
14CP1205195.42
15CCE_KLD1207195.36
16MSE_KLD1202995.37
EMNIST / СНС / 2'556'234
c15 = CR32-P-CR64-P-F-0.25-DR800-DL16 / 93.13 (86.54 – 94.40) / 24
c16 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL16 (план)
1MSE1201594.31
2MAE1202886.54
3MAPE1204890.35
4MSLE1201094.19
5SH1201894.16
6H1205586.69
7CH1205294.11
8LC1201694.32
9CCE1201694.17
10SCCE1201594.31
11BCE1203093.91
12KLD1201594.29
13PSS1201494.26
14CP1202594.32
15CCE_KLD1202994.27
16MSE_KLD12011/1294.28/94.29
17myLoss1202994.40
EMNIST / СНС / 2'567'812
c17 = CR32-P-CR64-P-F-0.25-DR800-DL30 / (82.69 – 94.40)
c18 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL30 (план)
1MSE601194.40
2MAE603386.23
3MAPE603090.95
4MSLE602994.36
5SH601594.25
6H602682.69
7CH602694.01
8LC602094.34
9CCE601894.22
10SCCE601594.29
11BCE601894.28
12KLD602094.34
13PSS601594.22
14CP602194.35
15CCE_KLD601894.37
16MSE_KLD601194.24
EMNIST / СНС / 3'262'506
c02 = CR32-P-CR64-P-F-DR1024-DL16 / 93.31 (86.73 – 94.19) / 19
1MSE1201193.96
2MAE30886.73
3MAPE604893.91
4MSLE301194.19
5SH30/6029/3293.91/93.84
6H906890.54
7CH302593.89
8LC301594.09
9CCE301893.84
10SCCE301193.87
11BCE306/1193.83/93.93 / -/97.6002
12KLD301593.81
13PSS30893.88
14CP301194.08
15CCE_KLD30/8012/1193.87/93.82
16MSE_KLD30/8011/1593.91/93.79
17myLoss20894.02
EMNIST / СНС / 3'277'220
c01 = CR32-P-CR64-P-F-DR1024-DL30 / 93.11 (86.45 – 94.17) / 16
c1 = CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 / (82.63 – 94.67)
1MSE120
80
11
27
94.15
94.61
2MAE50
80
43
31
86.45
83.47
3MAPE60
80
47
27
90.85
87.22
4MSLE30
80
10
20
94.12
94.64
5SH30
80
11
34
94.07
94.54
6H30/60
80
27/21
25
88.95/87.30
82.63
7CH30
80
15
48
93.97
94.05
8LC30
80
11
29
94.17
94.67
9CCE30
80
16
58
93.96
94.55
10SCCE30
80
15
44
94.01
94.56
11BCE20
80
8
16
94.04
94.44
12KLD20
80
8
15
94.05
94.55
13PSS30
80
8
15
94.02
94.63
14CP30
80
17
42
94.00
94.62
15CCE_KLD30
80
8
11
94.01
94.63
16MSE_KLD30
80
8
12
94.02
94.06
17myLoss30
80
8
-
94.08
-
EMNIST / СНС / 3'300'342
c03 = CR32-P-CR64-P-F-DR1024-DL52 / 93.43 (90.68 – 94.13) / 18
c3 = CR32-P-CR64-P-F-0.35-DR1024-0.2-DL52 / (85.93 – 94.75)
1MSE120
80
11
12
93.95
94.75
2MAE60
80
41
32
90.68
89.80
3MAPE60
80
43
28
90.89
85.93
4MSLE30
80
8
30
94.07
94.60
5SH30
80
13
16
94.00
94.01
6H60
80
39
39
90.81
90.70
7CH60
80
31
23
93.78
93.91
8LC30
80
14
46
93.87
94.65
9CCE20
80
6
30
94.02
94.59
10SCCE30
80
6
15
93.95
94.50
11BCE30
80
8
23
93.92
94.53
12KLD30
80
8
15
94.07
94.62
13PSS30
80
15
15
94.13
93.95
14CP30
80
18
23
94.09
94.68
15CCE_KLD30
80
14
15
93.89
94.50
16MSE_KLD30
80
7
15
94.04
94.01
17myLoss30
-
14
-
94.12
-
CIFAR-10 / СНС / 199'100
c5 = CR20-P-CR30-P-F-0.2-DR100 / (23.05 – 71.91) / 118
1MSE1205870.85
2MAE12011568.61
3MAPE12011869.36
4MSLE1205071.91
5SH12010369.78
6H12011368.09
7CH1204923.05
8LC12010071.09
9CCE1204171.30
10SCCE1209371.13
11BCE1205771.73
12KLD1206271.66
13PSS1202971.62
14CP1207071.13
15CCE_KLD1204871.73
16MSE_KLD1205771.57
CIFAR-10/ СНС / 273'066
c36 = ResNet20v1 / (67.43 – 91.62)
1MSE200139 / 15890.08 / 90.42
2MAE200159 / 19685.09 / 67.43
3MAPE200132 / 15169.20 / 80.24
4MSLE200188 / 16089.11 / 88.89
5SH200167 / 18690.14 / 89.62
6H200160 / 15569.06 / 84.06
7CH200148 / 18788.10 / 89.58
8LC200157 / 13789.15 / 88.80
9CCE200173 / 13290.70 / 90.88
9CCE (seedVal = 348)20014590.45
10SCCE200154 / 15590.66 / 90.98
10SCCE (seedVal = 348)20015590.49
11BCE200175 / 13491.30 / 91.05
11BCE (seedVal = 348)20015091.49
12KLD200138 / 18791.10 / 90.81
12KLD (seedVal = 348)20012590.60
13PSS200181 / 156 / 12691.15 / 91.62 / 91.41
13PSS (seedVal = 348)20014391.37
14CP200135 / 151 / 16690.72 / 91.35 / 90.59
14CP (seedVal = 348)20014290.78
15CCE_KLD200137 / 18390.23 / 90.39
16MSE_KLD200191 / 10490.59 / 90.24
CIFAR-10 / СНС / 272'778
c37 = ResNet32v1 / (89.06 – 92.02)
1MSE20018390.57
4MSLE20012689.06
9CCE20018491.45
10SCCE20014891.51
11BCE20012792.02
12KLD20018091.69
13PSS200130 / 17091.69 / 92.00
14CP20016291.20
CIFAR-10 / СНС / 292'955
c7 = CR10-P-CR15-P-F-0.2-DR300 / (64.31 – 71.07)
1MSE1205669.60
2MAE12011468.65
3MAPE12011969.25
4MSLE1203370.65
5SH1205469.38
6H12012068.26
7CH12010764.31
8LC1204370.42
9CCE1204270.32
10SCCE1203370.84
11BCE1203070.40
12KLD1203970.21
13PSS1203070.47
14CP1206670.33
15CCE_KLD1203171.07
16MSE_KLD1204270.91
CIFAR-10 / СНС / 1'082'490
c33 = CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.3-DL32-0.2 (20.26 – 75.88)
1MSE12011275.23
2MAE12011568.00
3MAPE1209968.19
4MSLE12011575.86
5SH12010673.26
6H1208767.25
7CH120120.26
8LC1208574.21
9CCE1203175.49
10SCCE1209975.16
11BCE1208775.88
12KLD1204474.69
13PSS1207974.79
14CP1209975.37
15CCE_KLD1208874.27
16MSE_KLD1207675.34
CIFAR-10 / СНС / 1'176'930
c4 = CR20-P-CR30-P-F-DR600-DL30 / (63.21 – 71.23)
c13 = CR20-P-CR30-P-F-0.25-DR600-DL30 / (23.27 – 72.62)
c14 = CR20-P-CR30-P-F-0.25-DR600-0.2-DL30 / (30.68 – 74.10)
2c14: 2'353'860 / (32.28 – 75.18)
3c14: 3'530'790 / (68.29 – 76.04)
1MSE120 22
33
120
119
102
69.96
72.20
72.39
73.91
74.80
2MAE120 85
110
120
86
118
69.16
71.01
71.50
73.92
74.41
3MAPE120 93
63
115
116
114
68.95
71.40
71.92
73.61
75.67
4MSLE120 21
21
34
53
73
69.54
71.54
72.96
73.16
75.28
5SH120 97
76
78
83
106
69.19
72.01
72.68
74.18
73.96
6H120 91
63
96
120
111
68.43
71.05
72.03
73.43
73.88
7CH120 115
03
01
1
102
63.21
23.27
30.68
32.28
68.29
8LC120 21
32
64
117
75
70.10
71.47
72.40
74.08
74.93
9CCE120 18
20
29
102
105
70.27
71.79
74.10
74.52
75.19
10SCCE120 42
19
31
84
60
71.23
71.63
73.13
74.80
74.84
11BCE120 12
20
42
106
73
70.61
72.57
73.56
74.14
75.35
12KLD120 17
19
27
72
101
69.75
72.26
73.61
74.20
75.08
13PSS120 16
21
32
51
116
70.15
71.67
73.57
74.02
75.07
14CP120 20
35
32
90
89
69.85
72.62
73.34
75.18
76.04
15CCE_KLD120 12
21
50
77
70
69.72
72.01
73.71
74.48
74.90
16MSE_KLD120 17
19
34
72
58
70.41
72.25
73.88
74.97
74.44
CIFAR-10 / СНС / 2'169'770 (84.13 / 84.13 – 84.96 / 85.31)
c34 = C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5
1MSE12099 / 11484.48 / 84.13
2MAE120104 / 10884.13 / 84.31
3MAPE120 / 180111 / 13984.31 / 84.39
4MSLE120 / 180115 / 13984.31 / 84.59
5SH120 / 180110 / 16984.67 / 84.41
6H180172 / 14084.32 / 84.75
7CH120 / 180102 / 15184.92 / 84.57
8LC180148 / 16184.62 / 84.52
9CCE180 161 / 17084.59 / 84.76
10SCCE120 / 180118 / 18084.40 / 84.38
11BCE180167 / 15784.96 / 85.31
12KLD180121 / 16984.56 / 84.98
13PSS120 / 180100 / 10384.57 / 84.50
14CP180118 / 15084.48 / 84.39
15CCE_KLD120 / 18080 / 15884.64 / 84.83
16MSE_KLD120 / 19080 / 17384.65 / 84.87
CIFAR-10 / СНС / 3'309'978
c15 = CR32-P-CR64-P-F-0.25-DR800-DL16 / (18.68 – 75.60)
c16 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL16 / (66.93 – 75.23)
1MSE12027
119
73.84
74.63
2MAE12084
101
72.94
67.64
3MAPE12079
68
73.35
74.01
4MSLE120119
116
68.80
66.93
5SH120109
30
69.98
74.32
6H120119
78
57.22
73.93
7CH1201
45
18.68
74.02
8LC120119
25
65.75
74.84
9CCE12019
43
74.38
75.34
10SCCE12028
18
74.51
74.86
11BCE120106
29
75.24
75.16
12KLD12024
29
73.38
75.16
13PSS120108
33
75.20
75.23
14CP12088
27
75.60
74.88
15CCE_KLD12017
37
74.43
75.14
16MSE_KLD12017
36
74.36
75.05
CIFAR-10 / СНС / 3'321'332
c17 = CR32-P-CR64-P-F-0.25-DR800-DL30 / (71.99 – 74.89)
c18 = CR32-P-CR64-P-F-0.25-DR800-0.2-DL30 / 71.09 – 75.61)
1MSE12016
34
74.04
75.38
2MAE12068
120
73.53
74.08
3MAPE12090
81
74.25
74.34
4MSLE12017
25
74.24
75.23
5SH12035
30
73.36
74.40
6H120106
117
73.41
73.99
7CH12069
114
71.99
71.09
8LC12018
23
74.21
75.01
9CCE12018
36
74.59
74.71
10SCCE12019
20
74.89
75.61
11BCE12042
42
74.48
75.30
12KLD12035
22
74.70
75.52
13PSS12017
20
74.42
74.42
14CP12030
18
74.24
74.71
15CCE_KLD12020
51
74.38
75.01
16MSE_KLD12016
16
74.16
75.41
CIFAR-10 / СНС / 4'245'780
c1 = CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30 (74.16 – 76.33)
1MSE1202175.92
2MAE12010374.70
3MAPE12011975.09
4MSLE1203876.12
5SH1206675.44
6H12010774.60
7CH1209774.16
8LC1203875.76
9CCE1203176.33
10SCCE1203076.30
11BCE1203275.79
12KLD1208976.03
13PSS1203676.07
14CP1202175.65
15CCE_KLD1202875.75
16MSE_KLD1206475.98

Замечание. Обозначениия НС приведены в табл. 4.

Приложение 2. Программа создания и обучения моделей НС

# Флаг задания режима воспроизведения результатов
repeat_results = False # True
seedVal = 348
import numpy as np
np.random.seed(seedVal) # Задание затравки датчика случайных чисел
#
if repeat_results:
    print('Режим воспроизведения (повторяемости) результатов')
    import random as rn
    import tensorflow as tf
    import os
    from keras import backend as K
    # Код, необходимый для воспроизведения результата
    os.environ['PYTHONHASHSEED'] = '0'
    rn.seed(seedVal)
    tf.set_random_seed(seedVal)
    session_conf = tf.ConfigProto(intra_op_parallelism_threads = 1,
                                 inter_op_parallelism_threads = 1)
    sess = tf.Session(graph = tf.get_default_graph(), config = session_conf)
    K.set_session(sess)
#
import time
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import keras
import keras.losses as ls
import keras.metrics as mt
import keras.callbacks as cb
from loadSaveShow import loadBinData, makeNames, load_data, load_cifar10, load_cifar100
from lossFuns import myLoss, cce_kld, mse17_kld
import sys # Для sys.exit()
#from keras.layers.embeddings import Embedding
#
def params():
    def makeModelLists(conv_list, modelNum): # Возвращает список слоев НС
        md = conv_list[modelNum - 1][1]
        md_list = md.split('-')
        return md_list
    #
    # Номер функции потерь - число из диапазона [1, 17]
    # 1 - mse; 2 - mae; 3 - mape; 4 - msle; 5 - sh; 6 - h; 7 - ch; 8 - lc;
    # 9 - cce; 10 - scce; 11 - bce; 12 - kld; 13 - pss; 14 - cp; 15 - myLoss
    # 16 - cce_kld; 17 - mse17_kld
    lossFunNum = 9
    # Виды методов оптимизации
    # 1 - SGD; 2 - RMSprop; 3 - Adagrad; 4 - Adadelta; 5 - Adam; 6 - Adamax; 7 - Nadam
    optMethodNum = 5
    # Вид модели НС: сверточная, многослойный перцептрон или рекуррентная
    nnTypeNum = 1 # 1 - conv; 2 - mlp; 3 - lstm
    # Номер набора данных
    dataSetNum = 1 # 1 - MNIST; 2 - EMNIST; 3 - CIFAR-10; 4 - CIFAR-100
    # Номер модели НС. Последний (выходной) слой опущен
    # Размер выхода 10 в случае MNIST и CIFAR-10, 26 в случае EMNIST, 100 в случае CIFAR-100
    # conv:
    conv_list = [
        [ '1', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30'],    [ '2', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL16'],
        [ '3', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL52'],    [ '4', 'CR20-P-CR30-P-F-DR600-DL30'],
        [ '5', 'CR20-P-CR30-P-F-0.2-DR100'],             [ '6', 'CR20-P-CR30-P-F-DR600'],
        [ '7', 'CR10-P-CR15-P-F-0.2-DR300'],             [ '8', 'CR10-P-CR15-P-F-0.25-DR300-DL30'],
        [ '9', 'CR20-P-CR30-P-F-DR600-DL16'],             ['10', 'CR20-P-CR30-P-F-DR600-DL52'],
        ['11', 'CR20-P-CR30-P-F-0.25-DR600-DL16'],         ['12', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL16'],
        ['13', 'CR20-P-CR30-P-F-0.25-DR600-DL30'],         ['14', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL30'],
        ['15', 'CR32-P-CR64-P-F-0.25-DR800-DL16'],         ['16', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL16'],
        ['17', 'CR32-P-CR64-P-F-0.25-DR800-DL30'],         ['18', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL30'],
        ['19', 'CR10-P-CR15-P-F-0.25-DR300'],             ['20', 'CR10-P-CR15-P-F-0.3-DR300'],
        ['21', 'CR10-P-CR15-P-F-0.35-DR300'],             ['22', 'CR10-P-CR15-P-F-0.3-DR300-0.3'],
        ['23', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2'], ['24', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2'],
        ['25', 'CR20-P-CR30-P-F-DR600-DL60'],             ['26', 'CR20-P-CR30-P-F-0.3-DR600-DL60'],
        ['27', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60'],     ['28', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2'],
        ['29', 'CR10-P-CR15-P-F-0.3-DR300-0.25-DL50-0.2'], ['30', 'CR20-P-0.25-CR30-CR30-P-0.25-F-DR512-0.5'],
        ['31', 'CR15-P-CR10-P-F-0.4-DR300'],             ['32', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.35-DR128-0.25'],
        ['33', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.3-DL32-0.2'],
        ['34', 'CR32-CR32-P-0.25-CR64-CR64-P-0.25-F-DR512-0.5'], # C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5
        ['35', 'BN-CR32-CR32-P-0.25-BN-CR64-CR64-P-0.25-F-DR512-0.5-DR128-0.25'],
        ['12bn', 'C20-BN-R-P-C30-BN-R-P-F-0.25-D600-BN-R-0.2-D16-BN-L'],
        ['28bn', 'C20-BN-R-P-C30-BN-R-P-F-0.3-D600-BN-R-0.2-D60-BN-L-0.2'],
        ['2bn', 'C32-BN-R-P-C64-BN-R-P-F-0.35-D1024-BN-R-0.2-D16-BN-L']]
    # mlp:
    mlp_list = [
        [ '1', 'DR800-DR80-DL30'],             [ '2', 'DR800-DR80-DL16'],
        [' 3', 'DR800-DR80-DL52'],             [ '4', '0.25-DR800-DR80-DL30'],
        [ '5', '0.25-DR800-0.25-DR80-DL30'],     [ '6', '0.25-DR800-0.25-DR80-0.2-DL30'],
        [ '7', '0.25-DR800-DR80-DL16'],         [ '8', '0.25-DR800-0.25-DR80-DL16'],
        [ '9', '0.25-DR800-0.25-DR80-0.2-DL16'], ['10', '0.25-DR800-DR80-DL52'],
        ['11', '0.25-DR800-0.25-DR80-DL52'],     ['12', '0.25-DR800-0.25-DR80-0.2-DL52']]
    # lstm:
    lstm_list = [['1', 'L300-0.2-DL30'], ['2', 'L500-0.3-DL30']]
    modelNum = 12 # Номер модели НС
    epochs = 120 # Число эпох обучения
    # Путь к данным
    p0 = 'G:/AM/НС/'
    if dataSetNum == 1: pathToData = p0 + 'mnist/'
    elif dataSetNum == 2: pathToData = p0 + 'emnist/'
    elif dataSetNum == 3: pathToData = p0 + 'cifar10/'
    elif dataSetNum == 4: pathToData = p0 + 'cifar100/'
    else:
        print('Плохой номер набора данных')
        sys.exit()
    #pathToData = 'D:\\Public\\Python\\'
    # Имя hdf5-файла с весами (и моделью, если при обучении weightsOnly = False)
    # Используется, когда loadModel = True
    fileWeights = 'weights_CIFAR10_lc_Adam_c5.119-7098' + '.hdf5'
    # Загрузка весов (и модели, если loadModel = True) из ранее сохраненного файла
    loadModel = False # True False
    # Число ветвей в сборке сетей (размер ансамбля)
    n_assems = 1 # 1, 2 или 3
    # Флаг, определяющий положение слоев BatchNormalization и Activation (перед или после сверточного слоя)
    BA_first = False # True False
    # Флаг использования инициализаторов he_uniform и glorot_uniform
    he_glorot = False # True False
    # Флаг использования batch-нормализации
    useBN = True # True False
    # Флаг использования регуляризации
    use_regularizers = False # True False
    # Флаг перемешивания данных пакета обучения
    use_shuffle = True # True False
    # Флаг использования сгенерированных данных (data augmentation в режиме реального времени)
    use_d_a = False # True False
    # Флаг раннего завершения обучения по метрике loss или val_acc
    use_earlyStopping = True # True False
    # Флаг использования метрики val_acc для ранней остановки
    use_earlyStoppingValAcc = True # True False
    # Случай трех входов: R - вход 1; G - вход 2; B - вход 3
    use_inp_3 = False # True False
    # Флаг обучения модели
    fitModel = False # True False
    if not loadModel: fitModel = True
    # Флаг сохранения и загрузки весов. Если False, то сохраняются (загружаются) и веса, и модель
    weightsOnly = True # True False
    # Флаг завершения после формирования модели
    onlyModel = True # True False
    # Флаг использования predict вместо evaluate
    predict = False # True False
    # Флаг сохранения модели
    saveModel = True # False
    # Флаг загрузки данных из предварительно созданных двоичных файлов
    loadFromBin = True # True False (только для MNIST и EMNIST)
    # Флаг вывода графика Точность в процессе обучения
    showFig = False # True False
    # Флаг вывода рисунков с данными; после вывода sys.exit()
    show_img = False # True False
    # Флаг использования и вывода рисунков тестового или обучающего набора данных
    useTestData = True # True False
    # Флаг вывода неправильно классифицированных рисунков (режим прогнозирования)
    showErrPics = True # True False
    # Флаг сохранения исходных данных в бинарные файлы
    saveData = False # True False
    # Флаг вывода структуры модели
    plotModel = False # True False
    use_inp_3 = False if dataSetNum < 3 else use_inp_3
    if use_inp_3:
        n_assems = 3
        use_d_a = False
    # Вычитать среднее RGB (вычисляется по x_train) из x_train и x_test (возможно повышение точности)
    subtract_pixel_mean = False
    #
    if n_assems not in [1, 2, 3]:
        print('Недопустимое число ветвей в сборке')
        sys.exit()
    # Вид модели НС
    if nnTypeNum == 1: nnType = 'conv'
    elif nnTypeNum == 2: nnType = 'mlp'
    elif nnTypeNum == 3: nnType = 'lstm'
    else:
        print('Плохой номер типа НС')
        sys.exit()
    # Набор данных (вид изображений)
    if dataSetNum == 1: imgType = 'MNIST'
    elif dataSetNum == 2: imgType = 'EMNIST'
    elif dataSetNum == 3: imgType = 'CIFAR10'
    elif dataSetNum == 4: imgType = 'CIFAR100'
    #
    # Размер обучающей порции - число экземпляров данных обучающей выборки
    # между пересчетами весовых коэффициентов
    # Уменьшение batch_size замедляет обучение, но может повысить его качество
    batch_size = 256
    #
    # Размер окна фильтра
    k_size = 0
    #
    if nnType == 'conv': # Сверточная НС
        if modelNum < 1 or modelNum > 35:
            print('Плохой номер модели сверточной НС')
            sys.exit()
        # Размер окна фильтра
        k_size = 3 if dataSetNum >= 3 else 4
        if modelNum in [30]: batch_size, k_size = 200, 3
    elif nnType == 'mlp': # Многослойный перцептрон
        if modelNum < 1 or modelNum > 12:
            print('Плохой номер модели полносвязной НС')
            sys.exit()
    elif nnType == 'lstm': # Рекуррентная НС
        if modelNum < 1 or modelNum > 2:
            print('Плохой номер модели рекуррентной НС')
            sys.exit()
    #
    # Получаем список слоев модели НС
    if nnTypeNum == 1:
        md_list = makeModelLists(conv_list, modelNum)
    elif nnTypeNum == 2:
        md_list = makeModelLists(mlp_list, modelNum)
    else:
        md_list = makeModelLists(lstm_list, modelNum)
    #
    # Число классов (по числу цифр в случае MNIST)
    if dataSetNum == 1 or dataSetNum == 3: # MNIST или CIFAR-10
        num_classes = 10
    elif dataSetNum == 2:
        num_classes = 26
    elif dataSetNum == 4: # CIFAR-100
        num_classes = 100
    # Размер входного образа
    if dataSetNum >= 3: # CIFAR-10, CIFAR-100
        img_rows = img_cols = 32
        img_size = img_rows * img_cols * 3
    else:
        img_rows = img_cols = 28
        img_size = img_rows * img_cols
    #
    # Форма входа
    if nnTypeNum == 1: # conv - случай сверточной НС
        input_shape = (img_rows, img_cols, 3 if dataSetNum >= 3 and not use_inp_3 else 1)
    elif nnTypeNum == 2: # mlp
        input_shape = (img_size, )
    else:
        input_shape = (img_rows, img_cols)
    allParams = []
    allParams.append(batch_size)         # 0
    allParams.append(num_classes)
    allParams.append(epochs)
    allParams.append(lossFunNum)
    allParams.append(use_earlyStopping)
    allParams.append(optMethodNum)     # 5
    allParams.append(img_rows)
    allParams.append(img_cols)
    allParams.append(weightsOnly)
    allParams.append(k_size)
    allParams.append(n_assems)         # 10
    allParams.append(md_list)
    allParams.append(modelNum)
    allParams.append(pathToData)
    allParams.append(loadFromBin)
    allParams.append(loadModel)         # 15
    allParams.append(saveModel)
    allParams.append(predict)
    allParams.append(showFig)
    allParams.append(show_img)
    allParams.append(saveData)         # 20
    allParams.append(plotModel)
    allParams.append(fitModel)
    allParams.append(showErrPics)
    allParams.append(nnType)
    allParams.append(onlyModel)         # 25
    allParams.append(fileWeights)
    allParams.append(useTestData)
    allParams.append(imgType)
    allParams.append(input_shape)
    allParams.append(useBN)             # 30
    allParams.append(use_inp_3)
    allParams.append(use_regularizers)
    allParams.append(subtract_pixel_mean)
    allParams.append(he_glorot)
    allParams.append(use_earlyStoppingValAcc) #35
    allParams.append(use_d_a)
    allParams.append(use_shuffle)
    allParams.append(BA_first)
    #
    print('Способ тестирования:', 'predict' if predict else 'evaluate')
    print('Набор данных:', imgType)
    return allParams
#
def createModel(allParams):
    def BN_A(x, batch_normalization, activation):
        if batch_normalization: x = BatchNormalization()(x)
        if activation is not None: x = Activation(activation)(x)
        return x
    #
    from keras.models import Model
    from keras.layers import Input, Dense, Dropout, average, Activation
    from keras.layers.normalization import BatchNormalization
    from keras import initializers
    num_classes = allParams[1]
    lossFunNum = allParams[3]
    optMethodNum = allParams[5]
    n_assems = allParams[10]
    md_list = allParams[11]
    modelNum = allParams[12]
    pathToData = allParams[13]
    loadModel = allParams[15]
    plotModel = allParams[21]
    nnType = allParams[24]
    onlyModel = allParams[25]
    imgType = allParams[28]
    input_shape = allParams[29]
    useBN = allParams[30]
    use_inp_3 = allParams[31]
    u_r = allParams[32] # use_regularizers
    he_glorot = allParams[34]
    BA_first = allParams[38]
    #
    if he_glorot:
        kn0_init = keras.initializers.he_uniform(seed = seedVal)
        kn0_print = 'he_uniform'
        kn0_init_out = keras.initializers.glorot_uniform(seed = seedVal)
        kn0_out_print = 'glorot_uniform'
    else:
        kn0_init = kn0_init_out = keras.initializers.RandomNormal(seed = seedVal)
        kn0_print = kn0_out_print = 'random_normal'
    print("Инициализатор слоев:", kn0_print)
    if kn0_print != kn0_out_print:
        print("Инициализатор классифицирующего слоя:", kn0_out_print)
    #
    # Функция потерь
    if lossFunNum == 1: loss = ls.mean_squared_error
    elif lossFunNum == 2: loss = ls.mean_absolute_error
    elif lossFunNum == 3: loss = ls.mean_absolute_percentage_error
    elif lossFunNum == 4: loss = ls.mean_squared_logarithmic_error
    elif lossFunNum == 5: loss = ls.squared_hinge
    elif lossFunNum == 6: loss = ls.hinge
    elif lossFunNum == 7: loss = ls.categorical_hinge
    elif lossFunNum == 8: loss = ls.logcosh
    elif lossFunNum == 9: loss = ls.categorical_crossentropy
    elif lossFunNum == 10: loss = ls.sparse_categorical_crossentropy
    elif lossFunNum == 11: loss = ls.binary_crossentropy
    elif lossFunNum == 12: loss = ls.kullback_leibler_divergence
    elif lossFunNum == 13: loss = ls.poisson
    elif lossFunNum == 14: loss = ls.cosine_proximity
    elif lossFunNum == 15: loss = myLoss
    elif lossFunNum == 16: loss = cce_kld
    elif lossFunNum == 17: loss = mse17_kld
    # Метрика
    # В случае binary_crossentropy используется keras.metrics.binary_accuracy,
    # что дает более высокие оценки точности по сравнению categorical_accuracy
    metrics = ['accuracy', mt.categorical_accuracy] if lossFunNum == 11 else ['accuracy']
    print('Метрика:', metrics)
    # Виды методов оптимизации:
    # SGD; RMSprop; Adagrad; Adadelta; Adam; Adamax; Nadam; TFOptimizer
    # Вариант задания метода оптимизации
    # from keras import optimizers
    # optKind = optimizers.SGD(lr = 0.01, decay = 1e-6, momentum = 0.9, nesterov = True)
    if optMethodNum == 1: optimizer = keras.optimizers.SGD()
    elif optMethodNum == 2: optimizer = keras.optimizers.RMSprop()
    elif optMethodNum == 3: optimizer = keras.optimizers.Adagrad()
    elif optMethodNum == 4: optimizer = keras.optimizers.Adadelta()
    elif optMethodNum == 5: optimizer = keras.optimizers.Adam()
    elif optMethodNum == 6: optimizer = keras.optimizers.Adamax()
    elif optMethodNum == 7: optimizer = keras.optimizers.Nadam()
    else:
        print("Плохой номер оптимизатора")
        sys.exit()
    #
    ub = True
    print('Номер модели НС:', modelNum)
    print('Модель НС:', md_list)
    if n_assems > 1: print('Число ветвей в НС:', n_assems)
    if useBN: print('Используется batch-нормализация')
    if u_r:
        from keras.regularizers import l2 # l1
        p_reg = 1e-4
        print('Используется регуляризация c коэффициентом', p_reg)
        k_reg = l2(p_reg)
        a_reg = None # l1(p_reg)
    else:
        k_reg = None
        a_reg = None
    if nnType == 'conv':
        from keras.layers import Conv2D, MaxPooling2D, Flatten
        print('Сверточная НС')
        nnTp = 'cnn'
        k_size = allParams[9]
        c_strides = 1
        p_size = p_strides = 2
        pad = 'same' # valid same
        #d_rate = 1 # dilation_rate = d_rate
        print('Kernel size:', k_size)
        print('Conv_strides:', c_strides)
        print('Pool_strides:', p_strides)
        print('Padding:', pad)
        print('Use bias:', ub)
    elif nnType == 'mlp':
        nnTp = 'mlp'
        print('Многослойный перцептрон')
    elif nnType == 'lstm':
        from keras.layers import LSTM
        print('Рекуррентная НС')
        nnTp = 'lstm'
    inp = [] if use_inp_3 else Input(shape = input_shape)
    assems = []
    for k in range(n_assems):
        if n_assems > 1: print('Номер ветви сборки:', k + 1)
        if use_inp_3: inp.append(Input(shape = input_shape))
        first_layer = True
        for md in md_list:
            if first_layer:
                x = inp[k] if use_inp_3 else inp
                first_layer = False
            m0 = md[0]
            if m0 == 'C':
                print('Filters:', int(md[2:]))
                cnv = Conv2D(int(md[2:]), kernel_size = k_size, strides = c_strides, padding = pad,
                             kernel_initializer = kn0_init, use_bias = ub, bias_initializer = kn0_init,
                             kernel_regularizer = k_reg, activity_regularizer = a_reg)
                if BA_first:
                    x = BN_A(x, useBN, 'relu')
                    x = cnv(x)
                else:
                    x = cnv(x)
                    x = BN_A(x, useBN, 'relu')
            elif m0 == 'P':
                print('Pooling')
                x = MaxPooling2D(pool_size = p_size, strides = p_strides, padding = pad)(x)
            elif m0 == '0':
                print('Dropout:', float(md))
                x = Dropout(float(md), seed = seedVal)(x)
            elif m0 == 'F':
                print('Flatten')
                x = Flatten()(x)
            elif m0 == 'D':
                activation = 'relu' if md[1] == 'R' else 'linear'
                print('Units:', int(md[2:]))
                dns = Dense(int(md[2:]), kernel_initializer = kn0_init,
                            use_bias = ub, bias_initializer = kn0_init,
                            kernel_regularizer = k_reg, activity_regularizer = a_reg)
                if BA_first:
                    x = BN_A(x, useBN, activation)
                    x = dns(x)
                else:
                    x = dns(x)
                    x = BN_A(x, useBN, activation)
                print('Activation:', activation)
            elif m0 == 'L':
                print('Units:', int(md[1:]))
                x = LSTM(int(md[1:]), kernel_initializer = kn0_init,
                         use_bias = ub, bias_initializer = kn0_init,
                         kernel_regularizer = k_reg, activity_regularizer = a_reg)(x)
        output = Dense(num_classes, activation = 'softmax', kernel_initializer = kn0_init_out,
                     use_bias = ub, bias_initializer = kn0_init_out,
                     kernel_regularizer = k_reg, activity_regularizer = a_reg)(x)
        assems.append(output)
    if n_assems > 1: output = average(assems)
    model = Model(inputs = inp, outputs = output)
    model.summary() # Вывод сведений о слоях НС
    if plotModel:
        from keras.utils import plot_model
        fileNm = pathToData + nnTp + '.png'
        print('Граф модели сохранен в файл ' + fileNm)
        plot_model(model, to_file = fileNm)
    if onlyModel:
        print('Только формирование и компиляция модели')
        sys.exit()
    model.compile(loss = loss, optimizer = optimizer, metrics = metrics)
    return model
#
# Проверка
def checkModel(model, x_train_test_0, x_train_test, y_train_test, allParams):
    lossFunNum = allParams[3]
    predict = allParams[17]
    useTestData = allParams[27]
    print('Оцениваем модель по', 'тестовым' if useTestData else 'обучающим', 'данным')
    start_time = time.time()
    if predict:
        print('Прогнозирование')
        img_rows = allParams[6]
        img_cols = allParams[7]
        pathToData = allParams[13]
        showErrPics = allParams[23]
        fileWeights = allParams[26]
        imgType = allParams[28]
        n_train_test = allParams[39]
        y_train_test_0 = allParams[40]
        y_pred = model.predict(x_train_test_0) # Предсказания модели НС на обучающей или тестовой выборке
        # Заносим в список classes метки классов, предсказанных моделью НС
        classes = []
        for m in y_pred:
            classes.append(np.argmax(m))
        # np.sum(classes == y_train_test_0) вернет сумму случаев, когда classes[i] = y_train_test_0[i]
        # Число верно классифицированных изображений
        nClassified = np.sum(classes == y_train_test_0)
        # Число ошиибочно классифицированных изображений
        nNotClassified = n_train_test - nClassified
        acc = 100.0 * nClassified / n_train_test
        print("Число ошибочно классифицированных образов: " + str(nNotClassified))
        print("Точность прогнозирования: " + str(acc) + '%')
        if showErrPics:
            p = fileWeights.find('_')
            p2 = fileWeights.find('.')
            file_name = pathToData + 'err_' + fileWeights[p + 1:p2] + '.txt'
            print('Список неверно классифицированных образов в файле ' + file_name)
            fn = open(file_name, 'w')
            names = makeNames(imgType) # Список с именами классов
            if imgType == 'CIFAR10': plt.subplots(5, 5, figsize = (7, 4))
            n = 0 # Число ошибочных предсказаний
            for i in range(n_train_test):
                s_true = names[y_train_test_0[i]] # Истинное имя класса
                s_pred = names[classes[i]] # Предсказанное моделью имя класса
                if s_true != s_pred:
                    n += 1
                    if (n > nNotClassified): break
                    str_i = str(i);
##                    print(str(n) + '. i = ' + str_i + '. На самом деле: ' + s_true + '. Прогноз: ' + s_pred)
                    fn.write(str_i + '\n')
                    if n < 26:
                        plt.subplot(5, 5, n)
                        if imgType == 'CIFAR10':
                            plt.imshow(x_test[i])
                        else:
                            plt.imshow(x_test[i].reshape(img_rows, img_cols), cmap = plt.get_cmap('gray'))
                        if imgType == 'CIFAR10':
                            title_font = {'fontname':'Arial', 'size':'9', 'color':'black'}
                            plt.title(s_true + '/' + s_pred, **title_font)
                        else:
                            plt.title(s_true + '/' + s_pred)
                        plt.axis('off')
            fn.close()
            plt.subplots_adjust(hspace = 0.5) # wspace
            plt.show()        
    else:
        print('Тестирование')
        score = model.evaluate(x_train_test_0, y_test, verbose = 0)
        # Вывод потерь и точности
        # binary_crossentropy (lossFunNum == 11)
        print('Потери при тестировании: ', score[0])
        print('Точность при тестировании:', score[2 if lossFunNum == 11 else 1])
    # Время тестирования / прогнозирования
    print('Время ' + ('прогнозирования: ' if predict else 'тестирования: '), (time.time() - start_time))
#
# Главная программа
#
allParams = params()
batch_size = allParams[0]
epochs = allParams[2]
lossFunNum = allParams[3]
use_earlyStopping = allParams[4]
optMethodNum = allParams[5]
weightsOnly = allParams[8]
pathToData = allParams[13]
loadModel = allParams[15]
saveModel = allParams[16]
predict = allParams[17]
showFig = allParams[18]
show_img = allParams[19]
fitModel = allParams[22]
onlyModel = allParams[25]
useTestData = allParams[27]
imgType = allParams[28]
use_inp_3 = allParams[31]
use_earlyStoppingValAcc = allParams[35]
use_d_a = allParams[36]
use_shuffle = allParams[37]
if use_inp_3: print('Случай трех входов: R - вход 1; G - вход 2; B - вход 3')
fileWeights = lossNm = optNm = suff = ''
if lossFunNum == 1: lossNm = 'mse'
elif lossFunNum == 2: lossNm = 'mae'
elif lossFunNum == 3: lossNm = 'mape'
elif lossFunNum == 4: lossNm = 'msle'
elif lossFunNum == 5: lossNm = 'sh'
elif lossFunNum == 6: lossNm = 'h'
elif lossFunNum == 7: lossNm = 'ch'
elif lossFunNum == 8: lossNm = 'lc'
elif lossFunNum == 9: lossNm = 'cce'
elif lossFunNum == 10: lossNm = 'scce'
elif lossFunNum == 11: lossNm = 'bce'
elif lossFunNum == 12: lossNm = 'kld'
elif lossFunNum == 13: lossNm = 'pss'
elif lossFunNum == 14: lossNm = 'cp'
elif lossFunNum == 15: lossNm = 'myLoss'
elif lossFunNum == 16: lossNm = 'ck' # cce_kld
elif lossFunNum == 17: lossNm = 'mk' # mse17_kld
else:
    print('Плохой номер функции потерь')
    sys.exit()
#
if optMethodNum == 1: optNm = 'SGD'
elif optMethodNum == 2: optNm = 'RMSprop'
elif optMethodNum == 3: optNm = 'Adagrad'
elif optMethodNum == 4: optNm = 'Adadelta'
elif optMethodNum == 5: optNm = 'Adam'
elif optMethodNum == 6: optNm = 'Adamax'
elif optMethodNum == 7: optNm = 'Nadam'
elif optMethodNum == 8: optNm = 'TFOptimizer'
else:
    print('Плохой номер метода оптимизации')
    sys.exit()
print('Функция потерь: ' + lossNm)
print('Метод оптимизации: ' + optNm)
if use_earlyStopping:
    if use_earlyStoppingValAcc:
        patience = 30
        monitorES = 'val_acc'
    else:
        patience = 9
        monitorES = 'loss'
    print('Используется ранняя остановка по метрике', monitorES, 'с параметром patience =', patience)
#
# Загрузка данных и формирование обучающих и тестовых выборок
if not onlyModel or show_img:
    if imgType == 'CIFAR10' or imgType == 'CIFAR100':
        if imgType == 'CIFAR10':
            x_train, y_train, x_test, y_test = load_cifar10(allParams)
        else:
            x_train, y_train, x_test, y_test = load_cifar100(allParams)
        if use_inp_3: # Случай трех входов: R - вход 1; G - вход 2; B - вход 3
            x_train_R = x_train[:, :, :, 0:1] # x_train_R.shape: (50000, 32, 32, 1)
            x_train_G = x_train[:, :, :, 1:2]
            x_train_B = x_train[:, :, :, 2:3]
            x_test_R = x_test[:, :, :, 0:1]
            x_test_G = x_test[:, :, :, 1:2]
            x_test_B = x_test[:, :, :, 2:3]
    else:
        x_train, y_train, x_test, y_test = load_data(allParams)
#
if loadModel:
    fileWeights = pathToData + allParams[26]
    if weightsOnly:
        model = createModel(allParams)
        print('Загрузка весов из файла ' + fileWeights)
        model.load_weights(fileWeights)
    else:
        print('Загрузка модели и весов из файла ' + fileWeights)
        model = keras.models.load_model(fileWeights)
        model.save_weights(fileWeights)
        print('Веса модели сохранены в файл ' + fileWeights)
        model.summary()
else:
    # Создаем модель нейронной сети
    model = createModel(allParams)
#
if fitModel:
    lossFunNum = allParams[3]
    modelNum = allParams[12]
    nnType = allParams[24]
    n_assems = allParams[10]
    nnType2 = nnType[0]; nnType2 = 'r' if nnType2 == 'l' else nnType2
    i_3 = '_inp_3' if use_inp_3 else ''
    n_a = '' if n_assems == 1 else str(n_assems)
    d_a = '_DA' if use_d_a else ''
    suff = imgType + '_' + lossNm + '_' + optNm + '_' + n_a + nnType2 + str(modelNum) + i_3+ d_a
    start_time = time.time()
    callbacks_list = []
    if saveModel:
        # Обеспечим сохранение обученной сети
        filesToSave = 'weights_' + suff + '.{epoch:03d}-{val_acc:.2f}.hdf5'
        say = 'веса' if weightsOnly else 'модель и веса'
        print('Сохраняем ' + say + ' в файлы вида ' + filesToSave)
        pathWeights = pathToData + filesToSave
        # Сохраняем и веса, и модель, если save_weights_only = False. В противном случае - только веса
        monitor = 'val_categorical_accuracy' if lossFunNum == 11 else 'val_acc'
        checkpoint = cb.ModelCheckpoint(pathWeights, monitor = monitor, verbose = 0,
                                                     save_weights_only = weightsOnly,
                                                     save_best_only = True, mode = 'max', period = 1)
        callbacks_list.append(checkpoint)
    if use_earlyStopping:
        callbacks_list.append(cb.EarlyStopping(monitor = monitorES, patience = patience))
    # Обучение
    # Запоминаем историю для вывода графиков потерь и точности, если saveModel = True
    if loadModel:
        print('Продолжаем обучение из файла:' + fileWeights)
    else:
        print('Обучение')
    print('Число эпох:', epochs)
    print('Размер пакета обучения:', batch_size)
    if use_shuffle:
        print('Выполняется перемешивание данных пакета обучения')
    if use_inp_3:
        history = model.fit([x_train_R, x_train_G, x_train_B], y_train, batch_size = batch_size, epochs = epochs,
                            verbose = 2, validation_data = ([x_test_R, x_test_G, x_test_B], y_test),
                            shuffle = use_shuffle, callbacks = callbacks_list)
    else:
        if not use_d_a:
            history = model.fit(x_train, y_train, batch_size = batch_size, epochs = epochs,
                                verbose = 2, validation_data = (x_test, y_test),
                                shuffle = use_shuffle, callbacks = callbacks_list)
        else:
            from keras.preprocessing.image import ImageDataGenerator
            print('Обучение по сгенерированным в режиме реального времени данным (data augmentation)')
            datagen = ImageDataGenerator(
                # Нулевое среднее значение входных данных по всему набору (False - значит не используем)
                featurewise_center = False,
                # Нулевое среднее значение по каждому экземпляру данных (каждому изображению)
                samplewise_center = False,
                # Делим вход на его сренеквадратическое значение
                featurewise_std_normalization = False,
                # Делим каждый экземпляр (изображение) на его сренеквадратическое значение
                samplewise_std_normalization = False,
                # ZCA-отбеливание
                zca_whitening = False,
                # epsilon для ZCA
                zca_epsilon = 1e-06,
                # Угол (в градусах) случайного поворота изображения; берется из диапазона (0 - 180)
                rotation_range = 0,
                # Случайный горизонтальный сдвиг изображения
                width_shift_range = 0.1,
                # Случайный вертикальный сдвиг изображения
                height_shift_range = 0.1,
                # Диапазон сдвига пикселей изображения (угол сдвига в градусах в направлении против часовой стрелки)
                shear_range = 0.,
                # Диапазон случайного выбора масштабирования изображения
                zoom_range = 0.,
                # set range for random channel shifts
                channel_shift_range = 0.,
                # Способ заполнения точек за пределами границы входных изображений
                fill_mode = 'nearest',
                # Значение для точек за пределами границы изображения (используется, когда fill_mode = 'constant')
                cval = 0.,
                # Если True, то выполняется случайный горизонтальный флип изображений
                # (поворот относительно оси y на 180 градусов; ось проходит через центр изображения)
                horizontal_flip = True,
                # То же для вертикального флипа
                vertical_flip = False,
                # Показатель масштабирования данных (применяется после всех прочих преобразований)
                rescale = None,
                # Функция, которая будет применена к каждому изображению
                preprocessing_function = None,
                # Формат данных ('channels_first' или 'channels_last')
                data_format = None,
                # Доля изображений, резервируемая для оценки качества модели; выбирается из диапазона (0 - 1)
                validation_split = 0.0)
            #
            # Вычисляем величины, необходимые для нормализации
            # (std, mean и principal components, если используется ZCA)
            datagen.fit(x_train)
            # Обучаем модель на данных, сгенерированных datagen.flow()
            history = model.fit_generator(datagen.flow(x_train, y_train, batch_size = batch_size),
                                         validation_data = (x_test, y_test),
                                         epochs = epochs, verbose = 2, workers = 4,
                                         callbacks = callbacks_list)
    print('Время обучения:', (time.time() - start_time))
    #
    suff += '.txt'
    f_loss = 'loss_' + suff
    f_acc = 'acc_' + suff
    f_val_loss = 'val_loss_' + suff
    f_val_acc = 'val_acc_' + suff
    # Вывод истории в файлы
    print('История сохранена в файлы ' + f_loss + '\r\n' + f_acc + '\r\n' + f_val_loss + '\r\n' + f_val_acc)
    if lossFunNum == 11: # binary_crossentropy
        acc = 'categorical_accuracy'
        val_acc = 'val_categorical_accuracy'
    else:
        acc = 'acc'
        val_acc = 'val_acc'
    with open(pathToData + f_loss, 'w') as output:
        for val in history.history['loss']: output.write(str(val) + '\r\n')
    with open(pathToData + f_acc, 'w') as output:
        for val in history.history[acc]: output.write(str(val) + '\r\n')
    with open(pathToData + f_val_loss, 'w') as output:
        for val in history.history['val_loss']: output.write(str(val) + '\r\n')
    with open(pathToData + f_val_acc, 'w') as output:
        for val in history.history[val_acc]: output.write(str(val) + '\r\n')
    #
    if showFig and not predict:
        # Вывод графиков
        yMax = max(history.history['acc'])
        cnt = len(history.history['acc'])
        rng = np.arange(cnt)
        fig, ax = plt.subplots(figsize = (6.2, 3.8))
        ax.scatter(rng, history.history['acc'], marker = 'o', c = 'blue', edgecolor = 'black', label = 'Точность')
        ##ax.scatter(rng, history.history['loss'], marker = 'o', c = 'red', edgecolor = 'black', label = 'Потери')
        ##ax.set_title('Потери и Точность в процессе обучения')
        ##ax.legend(loc = 'upper left')
        ##ax.set_ylabel('Потери, Точность')
        ax.set_title('Точность в процессе обучения')
        ax.set_ylabel('Точность')
        ax.set_xlabel('Эпоха - 1')
        ax.set_xlim([0.9, 1.1 * (cnt - 1)])
        ax.set_ylim([0, 1.1 * yMax])
        fig.show()
# Проверка
if useTestData:
    x_test_0 = [x_test_R, x_test_G, x_test_B] if use_inp_3 else x_test
    checkModel(model, x_test_0, x_test, y_test, allParams)
else:
    x_train_0 = [x_train_R, x_train_G, x_train_B] if use_inp_3 else x_train
    checkModel(model, x_train_0, x_train, y_train, allParams)

Приложение 3. Программа загрузки и тестирования обученных моделей НС

import numpy as np
import time
import keras
from loadSaveShow import loadBinData, makeNames, load_data, load_cifar10
import sys # Для sys.exit()
#
seedVal = 348
#
def params():
    def makeModelLists(conv_list, modelNum): # Возвращает список слоев НС
        md = conv_list[modelNum - 1][1]
        md_list = md.split('-')
        return md_list
    # Вид модели НС: сверточная, многослойный перцептрон или рекуррентная
    nnTypeNum = 1 # 1 - conv; 2 - mlp; 3 - lstm
    # Номер набора данных
    dataSetNum = 3 # 1 - MNIST; 2 - EMNIST; 3 - CIFAR10; 4 - CIFAR100
    # Номер модели НС. Последний (выходной) слой опущен
    # Размер выхода 10 или 26 в случае EMNIST
    conv_list = [
        [ '1', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30'],    [ '2', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL16'],
        [ '3', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL52'],    [ '4', 'CR20-P-CR30-P-F-DR600-DL30'],
        [ '5', 'CR20-P-CR30-P-F-0.2-DR100'],             [ '6', 'CR20-P-CR30-P-F-DR600'],
        [ '7', 'CR10-P-CR15-P-F-0.2-DR300'],             [ '8', 'CR10-P-CR15-P-F-0.25-DR300-DL30'],
        [ '9', 'CR20-P-CR30-P-F-DR600-DL16'],             ['10', 'CR20-P-CR30-P-F-DR600-DL52'],
        ['11', 'CR20-P-CR30-P-F-0.25-DR600-DL16'],         ['12', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL16'],
        ['13', 'CR20-P-CR30-P-F-0.25-DR600-DL30'],         ['14', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL30'],
        ['15', 'CR32-P-CR64-P-F-0.25-DR800-DL16'],         ['16', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL16'],
        ['17', 'CR32-P-CR64-P-F-0.25-DR800-DL30'],         ['18', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL30'],
        ['19', 'CR10-P-CR15-P-F-0.25-DR300'],             ['20', 'CR10-P-CR15-P-F-0.3-DR300'],
        ['21', 'CR10-P-CR15-P-F-0.35-DR300'],             ['22', 'CR10-P-CR15-P-F-0.3-DR300-0.3'],
        ['23', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2'], ['24', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2'],
        ['25', 'CR20-P-CR30-P-F-DR600-DL60'],             ['26', 'CR20-P-CR30-P-F-0.3-DR600-DL60'],
        ['27', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60'],     ['28', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2'],
        ['29', 'CR10-P-CR15-P-F-0.3-DR300-0.25-DL50-0.2'], ['30', 'CR20-P-0.25-CR30-CR30-P-0.25-F-DR512-0.5'],
        ['31', 'CR15-P-CR10-P-F-0.4-DR300'],             ['32', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.35-DR128-0.25'],
        ['33', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.3-DL32-0.2'],
        ['34', 'CR32-CR32-P-0.25-CR64-CR64-P-0.25-F-DR512-0.5'], # C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5
        ['35', 'BN-CR32-CR32-P-0.25-BN-CR64-CR64-P-0.25-F-DR512-0.5-DR128-0.25'],
        ['12bn', 'C20-BN-R-P-C30-BN-R-P-F-0.25-D600-BN-R-0.2-D16-BN-L'],
        ['28bn', 'C20-BN-R-P-C30-BN-R-P-F-0.3-D600-BN-R-0.2-D60-BN-L-0.2'],
        ['2bn', 'C32-BN-R-P-C64-BN-R-P-F-0.35-D1024-BN-R-0.2-D16-BN-L']]
    # mlp:
    mlp_list = [
        [ '1', 'DR800-DR80-DL30'],             [ '2', 'DR800-DR80-DL16'],
        [' 3', 'DR800-DR80-DL52'],             [ '4', '0.25-DR800-DR80-DL30'],
        [ '5', '0.25-DR800-0.25-DR80-DL30'],     [ '6', '0.25-DR800-0.25-DR80-0.2-DL30'],
        [ '7', '0.25-DR800-DR80-DL16'],         [ '8', '0.25-DR800-0.25-DR80-DL16'],
        [ '9', '0.25-DR800-0.25-DR80-0.2-DL16'], ['10', '0.25-DR800-DR80-DL52'],
        ['11', '0.25-DR800-0.25-DR80-DL52'],     ['12', '0.25-DR800-0.25-DR80-0.2-DL52']]
    # lstm:
    lstm_list = [['1', 'L300-0.2-DL30'], ['2', 'L500-0.3-DL30']]
    modelNum = 13
    # Путь к данным
    p0 = 'G:/AM/НС/'
    if dataSetNum == 1: pathToData = p0 + 'mnist/'
    elif dataSetNum == 2: pathToData = p0 + 'emnist/'
    elif dataSetNum == 3: pathToData = p0 + 'cifar10/'
    elif dataSetNum == 4: pathToData = p0 + 'cifar100/'
    else:
        print('Плохой номер набора данных')
        sys.exit()
##    pathToData = 'D:\\Public\\Python\\'
#    Если pathToWeights = '', тогда с fileWeights используется pathToData
    pathToWeights = 'G:/AM/НС/_результаты/cifar_results/w_' + str(modelNum) + 'w/'
    pathToWeights = 'G:/AM/НС/_результаты/emnist_results/2w_28_2w/'
##    pathToWeights = 'G:/AM/НС/_результаты/mnist_results/w_7w/'
##    pathToWeights = ''
    # Имя hdf5-файла с весами (и моделью, если при обучении weightsOnly = False)
    fileWeights = 'weights_EMNIST_lc_Adam_2c28.94-9494-98123397436' + '.hdf5'
##    fileWeights = 'weights_CIFAR10_lc_Adam_c5.119-7098' + '.hdf5'
    # Флаг использования batch-нормализации
    useBN = False # True False
    # Число ветвей в сборке сетей (размер ансамбля)
    n_assems = 1 # 1, 2 или 3
    # Флаг завершения после формирования модели
    onlyModel = True # True False
    # Флаг использования predict вместо evaluate
    predict = True # True False
    # Флаг загрузки данных из предварительно созданных двоичных файлов
    loadFromBin = True # True False (только для MNIST и EMNIST)
    # Флаг вывода структуры модели
    plotModel = False # True False
    # Флаг вывода неправильно классифицированных рисунков (режим прогнозирования)
    showErrPics = True # True False
    # Флаг вывода рисунков с данными; после вывода sys.exit()
    show_img = False # True False
    # Флаг использования тестового или обучающего набора данных
    useTestData = True # True False
    # Флаг преобразования меток в категориальное представление
    # Имеет значение, если predict = False
    categorical = True # False для sparse_categorical_crossentropy
    lossFunNum = 1 if categorical else 10
    # Случай трех входов: R - вход 1; G - вход 2; B - вход 3
    inp_3 = False # True False
    inp_3 = False if dataSetNum < 3 else inp_3
    n_assems = 3 if inp_3 else n_assems
    #
    if n_assems not in [1, 2, 3]:
        print('Недопустимое число ветвей в сборке')
        sys.exit()
    # Вид модели НС
    if nnTypeNum == 1: nnType = 'conv'
    elif nnTypeNum == 2: nnType = 'mlp'
    elif nnTypeNum == 3: nnType = 'lstm'
    else:
        print('Плохой номер типа НС')
        sys.exit()
    # Вид изображений
    if dataSetNum == 1: imgType = 'MNIST'
    elif dataSetNum == 2: imgType = 'EMNIST'
    elif dataSetNum == 3: imgType = 'CIFAR10'
    elif dataSetNum == 4: imgType = 'CIFAR100'
    #
    # Размер окна фильтра
    k_size = 0
    #
    if nnType == 'conv': # Сверточная НС
        if modelNum < 1 or modelNum > 32:
            print('Плохой номер модели сверточной НС')
            sys.exit()
        # Размер окна фильтра
        k_size = 3 if dataSetNum == 3 else 4
        if modelNum == 30: k_size = 3
    elif nnType == 'mlp': # Многослойный перцептрон
        if modelNum < 1 or modelNum > 12:
            print('Плохой номер модели полносвязной НС')
            sys.exit()
    elif nnType == 'lstm': # Рекуррентная НС
        if modelNum < 1 or modelNum > 2:
            print('Плохой номер модели рекуррентной НС')
            sys.exit()
    #
    # Получаем список слоев модели НС
    if nnTypeNum == 1:
        md_list = makeModelLists(conv_list, modelNum)
    elif nnTypeNum == 2:
        md_list = makeModelLists(mlp_list, modelNum)
    else:
        md_list = makeModelLists(lstm_list, modelNum)
    #
    # Число классов (по числу цифр в случае MNIST)
    if dataSetNum == 1 or dataSetNum == 3: # MNIST или CIFAR-10
        num_classes = 10
    elif dataSetNum == 2:
        num_classes = 26
    elif dataSetNum == 4: # CIFAR-100
        num_classes = 100
    # Размер входного образа
    if dataSetNum >= 3: # CIFAR-10 или CIFAR100
        img_rows = img_cols = 32
        img_size = img_rows * img_cols * 3
    else:
        img_rows = img_cols = 28
        img_size = img_rows * img_cols
    # Форма входа
    if nnTypeNum == 1: # conv - случай сверточной НС
        input_shape = (img_rows, img_cols, 3 if dataSetNum >= 3 and not inp_3 else 1)
    elif nnTypeNum == 2: # mlp
        input_shape = (img_size, )
    else:
        input_shape = (img_rows, img_cols)
    allParams = []
    allParams.append(0) # batch_size
    allParams.append(num_classes)
    allParams.append(0) # epochs
    allParams.append(lossFunNum)
    allParams.append(0) # earlyStopping
    allParams.append(0) # optMethodNum
    allParams.append(img_rows)            # 6
    allParams.append(img_cols)
    allParams.append(0) # weightsOnly
    allParams.append(k_size)
    allParams.append(n_assems)            # 10
    allParams.append(md_list)
    allParams.append(modelNum)
    allParams.append(pathToData)
    allParams.append(loadFromBin)
    allParams.append(pathToWeights)     # 15 (loadModel)
    allParams.append(0) # saveModel
    allParams.append(predict)
    allParams.append(0) # showFig
    allParams.append(show_img)
    allParams.append(0) # saveData
    allParams.append(plotModel)         # 21
    allParams.append(0) # fitModel
    allParams.append(showErrPics)
    allParams.append(nnType)
    allParams.append(onlyModel)         # 25
    allParams.append(fileWeights)
    allParams.append(useTestData)
    allParams.append(imgType)
    allParams.append(input_shape)
    allParams.append(useBN)             # 30
    allParams.append(inp_3)
    allParams.append(False)             # use_regularizers
    allParams.append(False)             # subtract_pixel_mean            
    allParams.append(False)             # he_glorot
    allParams.append(False)             # 35 (use_earlyStoppingValAcc)
    allParams.append(False)             # use_d_a
    allParams.append(False)             # use_shuffle
    allParams.append(False)             # BA_first
    print('Способ тестирования:', 'predict' if predict else 'evaluate')
    print('Набор данных:', imgType)
    return allParams
#
def createModel(allParams):
    from keras import initializers
    from keras.models import Model
    from keras.layers import Input, Dense, Dropout, average
    from keras.layers.normalization import BatchNormalization
    num_classes = allParams[1]
    n_assems = allParams[10]
    md_list = allParams[11]
    modelNum = allParams[12]
    pathToData = allParams[13]
    plotModel = allParams[21]
    nnType = allParams[24]
    onlyModel = allParams[25]
    imgType = allParams[28]
    input_shape = allParams[29]
    useBN = allParams[30]
    inp_3 = allParams[31]
    kn0_init = keras.initializers.RandomNormal(seed = seedVal)
    # Функция потерь и метод оптимизации (не имеют значения)
    loss = keras.losses.mean_squared_error
    optimizer = keras.optimizers.Adam()
    ub = True
    print('Номер модели НС:', modelNum)
    print('Модель НС:', md_list)
    if n_assems > 1: print('Число ветвей в НС:', n_assems)
    if useBN: print('Используется batch-нормализация')
    if nnType == 'conv':
        from keras.layers import Conv2D, MaxPooling2D, Flatten
        print('Сверточная НС')
        nnTp = 'cnn'
        k_size = allParams[9]
        c_strides = 1
        p_size = p_strides = 2
        pad = 'same' # valid same
        #d_rate = 1 # dilation_rate = d_rate
        #reg = keras.regularizers.l1_l2()
        print('Kernel size:', k_size)
        print('Conv_strides:', c_strides)
        print('Pool_strides:', p_strides)
        print('Padding:', pad)
        print('Use bias:', ub)
    elif nnType == 'mlp':
        nnTp = 'mlp'
        print('Многослойный перцептрон')
    elif nnType == 'lstm':
        from keras.layers import LSTM
        print('Рекуррентная НС')
        nnTp = 'lstm'
    inp = [] if inp_3 else Input(shape = input_shape)
    assems = []
    for k in range(n_assems):
        if n_assems > 1: print('Номер ветви сборки:', k + 1)
        if inp_3: inp.append(Input(shape = input_shape))
        first_layer = True
        for md in md_list:
            if first_layer:
                cur_l = inp[k] if inp_3 else inp
                first_layer = False
            m0 = md[0]
            if m0 == 'C':
                if useBN: cur_l = BatchNormalization(axis = -1)(cur_l)
                print('Filters:', int(md[2:]))
                cur_l = Conv2D(int(md[2:]), kernel_size = k_size, strides = c_strides,
                             padding = pad, activation = 'relu',
                             kernel_initializer = kn0_init, use_bias = ub, bias_initializer = kn0_init)(cur_l)
            elif m0 == 'P':
                print('Pooling')
                cur_l = MaxPooling2D(pool_size = p_size, strides = p_strides, padding = pad)(cur_l)
            elif m0 == '0':
                print('Dropout:', float(md))
                cur_l = Dropout(float(md), seed = seedVal)(cur_l)
            elif m0 == 'F':
                print('Flatten')
                cur_l = Flatten()(cur_l)
            elif m0 == 'D':
                activation = 'relu' if md[1] == 'R' else 'linear'
                if useBN: cur_l = BatchNormalization(axis = -1)(cur_l)
                print('Units:', int(md[2:]))
                cur_l = Dense(int(md[2:]), activation = activation, kernel_initializer = kn0_init,
                             use_bias = ub, bias_initializer = kn0_init)(cur_l)
                print('Activation:', activation)
            elif m0 == 'L':
                print('Units:', int(md[1:]))
                cur_l = LSTM(int(md[1:]), kernel_initializer = kn0_init,
                            use_bias = ub, bias_initializer = kn0_init)(cur_l)
        output = Dense(num_classes, activation = 'softmax', kernel_initializer = kn0_init,
                     use_bias = ub, bias_initializer = kn0_init)(cur_l)
        assems.append(output)
    if n_assems > 1: output = average(assems)
    model = Model(inputs = inp, outputs = output)
    model.summary() # Вывод сведений о слоях НС
    if plotModel:
        from keras.utils import plot_model
        fileNm = pathToData + nnTp + '.png'
        print('Граф модели сохранен в файл ' + fileNm)
        plot_model(model, to_file = fileNm)
    if onlyModel:
        print('Только формирование и компиляция модели')
        sys.exit()
    model.compile(loss = loss, optimizer = optimizer, metrics = ['accuracy'])
    return model
#
# Проверка
def checkModel(model, X_train_test_0, X_train_test, y_train_test, allParams):
    import matplotlib.pyplot as plt
    import matplotlib.font_manager as font_manager
    #
    def writeToBin(file_name, y, txt):
        fp = open(file_name, 'wb')
        fp.write(y)
        fp.close()
        print(txt + file_name)
    predict = allParams[17]
    useTestData = allParams[27]
    print('Оцениваем модель по', 'тестовым' if useTestData else 'обучающим', 'данным')
    start_time = time.time()
    if predict:
        print('Прогнозирование')
        img_rows = allParams[6]
        img_cols = allParams[7]
        pathToData = allParams[13]
        showErrPics = allParams[23]
        fileWeights = allParams[26]
        imgType = allParams[28]
        n_train_test = allParams[39]
        y_train_test_0 = allParams[40]
        #
        y_pred = model.predict(X_train_test_0) # Предсказания модели НС на обучающей или тестовой выборке
        #
        p = fileWeights.find('_')
        p2 = fileWeights.find('.')
        fn = fileWeights[p + 1:p2]
        #writeToBin(pathToData + 'y_pred_' + fn + '.bin', y_pred, 'Прогноз сохранен в файле ')
        # Заносим в список classes метки классов, предсказанных моделью НС
        classes = []
        for m in y_pred:
            classes.append(np.argmax(m))
        # Число верно классифицированных изображений
        nClassified = np.sum(classes == y_train_test_0)
        # Число ошиибочно классифицированных изображений
        nNotClassified = n_train_test - nClassified
        acc = 100.0 * nClassified / n_train_test
        print("Число ошибочно классифицированных образов: " + str(nNotClassified))
        print("Точность прогнозирования: " + str(acc) + '%')
        if showErrPics:
            file_name = pathToData + 'err_' + fn + '.txt'
            print('Список неверно классифицированных изображений сохранен в файл ' + file_name)
            fn = open(file_name, 'w')
            names = makeNames(imgType) # Список с именами классов
            if imgType == 'CIFAR10': plt.subplots(5, 5, figsize = (7, 4))
            n = 0 # Число ошибочных предсказаний
            for i in range(n_train_test):
                s_true = names[y_train_test_0[i]] # Истинное имя класса
                s_pred = names[classes[i]] # Предсказанное моделью имя класса
                if s_true != s_pred:
                    n += 1
                    if (n > nNotClassified): break
                    str_i = str(i);
    ##                print(str(n) + '. i = ' + str_i + '. На самом деле: ' + s_true + '. Прогноз: ' + s_pred)
                    fn.write(str_i + '\n')
                    if n < 26:
                        plt.subplot(5, 5, n)
                        if imgType[0:5] == 'CIFAR':
                            plt.imshow(X_train_test[i])
                        else:
                            plt.imshow(X_train_test[i].reshape(img_rows, img_cols), cmap = plt.get_cmap('gray'))
                        if imgType == 'CIFAR10':
                            title_font = {'fontname':'Arial', 'size':'9', 'color':'black'}
                            plt.title(s_true + '/' + s_pred, **title_font)
                        else:
                            plt.title(s_true + '/' + s_pred)
                        plt.axis('off')
            fn.close()
            plt.subplots_adjust(hspace = 0.5) # wspace
            plt.show()
    else:
        print('Тестирование')
        start_time = time.time()
        score = model.evaluate(X_train_test_0, y_train_test, verbose = 0)
        # Вывод потерь и точности
        print('Потери при тестировании: ', score[0])
        print('Точность при тестировании:', score[1])
    # Время тестирования / прогнозирования
    print('Время ' + ('прогнозирования: ' if predict else 'тестирования: '), (time.time() - start_time))
#
# Главная программа
#
allParams = params()
lossFunNum = allParams[3]
pathToData = allParams[13]
pathToWeights = allParams[15]
show_img = allParams[19]
onlyModel = allParams[25]
useTestData = allParams[27]
imgType = allParams[28]
inp_3 = allParams[31]
if inp_3: print('Случай трех входов: R - вход 1; G - вход 2; B - вход 3')
lossNm = 'Не имеет значения; берется mse' if lossFunNum == 1 else 'scce'
print('Функция потерь: ' + lossNm)
#
# Загрузка данных и формирование обучающих и тестовых выборок
if not onlyModel or show_img:
    if imgType == 'CIFAR10' or imgType == 'CIFAR100':
        if imgType == 'CIFAR10':
            X_train, y_train, X_test, y_test = load_cifar10(allParams)
        else:
            X_train, y_train, X_test, y_test = load_cifar100(allParams)
        if inp_3: # Случай трех входов: R - вход 1; G - вход 2; B - вход 3
            X_train_R = X_train[:, :, :, 0:1] # X_train_R.shape: (50000, 32, 32, 1)
            X_train_G = X_train[:, :, :, 1:2]
            X_train_B = X_train[:, :, :, 2:3]
            X_test_R = X_test[:, :, :, 0:1]
            X_test_G = X_test[:, :, :, 1:2]
            X_test_B = X_test[:, :, :, 2:3]
    else:
        X_train, y_train, X_test, y_test = load_data(allParams)
#
if len(pathToWeights) == 0:
    fileWeights = pathToData + allParams[26]
else:
    fileWeights = pathToWeights + allParams[26]
model = createModel(allParams)
print('Загрузка весов из файла ' + fileWeights)
model.load_weights(fileWeights)
# Проверка
if useTestData:
    X_test_0 = [X_test_R, X_test_G, X_test_B] if inp_3 else X_test
    checkModel(model, X_test_0, X_test, y_test, allParams)
else:
    X_train_0 = [X_train_R, X_train_G, X_train_B] if inp_3 else X_train
    checkModel(model, X_train_0, X_train, y_train, allParams)

Приложение 4. Общие процедуры программ создания, обучения, загрузки и тестирования моделей НС

import numpy as np
import matplotlib.pyplot as plt
import keras
import sys # Для sys.exit()
#from keras.datasets import mnist
#
def loadBinData(pathToData):
    print('Загрузка данных из двоичных файлов...')
    with open(pathToData + 'imagesTrain.bin', 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTrain.bin', 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'imagesTest.bin', 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(pathToData + 'labelsTest.bin', 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    return data, labels, data2, labels2
#
def load_mnist(pathToData, saveData):
    from mnist import MNIST
    print('Загрузка данных из исходных файлов...')
    mndata = MNIST(pathToData)
    # Разрешаем чтение архивированных данных
    mndata.gz = True
    # Обучающая выборка (данные и метки)
    imagesTrain, labelsTrain = mndata.load_training()
    # Тестовая выборка (данные и метки)
    imagesTest, labelsTest = mndata.load_testing()
    if saveData:
        fn = open(pathToData + 'imagesTrain.bin', 'wb')
        fn2 = open(pathToData + 'labelsTrain.bin', 'wb')
        fn3 = open(pathToData + 'imagesTest.bin', 'wb')
        fn4 = open(pathToData + 'labelsTest.bin', 'wb')
        fn.write(np.uint8(imagesTrain))
        fn2.write(np.uint8(labelsTrain))
        fn3.write(np.uint8(imagesTest))
        fn4.write(np.uint8(labelsTest))
        fn.close()
        fn2.close()
        fn3.close()
        fn4.close()
        print('MNIST-данные сохранены в двоичные файлы')
        # При работе с MNIST данные можно ввести, используя класс mnist, определенный в keras.datasets, но это медленнее
        #(X_train, y_train), (X_test, y_test) = mnist.load_data()
    return imagesTrain, labelsTrain, imagesTest, labelsTest
#
def makeNames(imgType):
    names = []
    if imgType == 'MNIST':
        for i in range(10): names.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
    elif imgType == 'EMNIST':
        for i in range(26): names.append(chr(65 + i)) # ['A', 'B', 'C', ..., 'Z']
    elif imgType == 'CIFAR10':
        names.append('Самолет')
        names.append('Авто')
        names.append('Птица')
        names.append('Кошка')
        names.append('Олень')
        names.append('Собака')
        names.append('Лягушка')
        names.append('Лошадь')
        names.append('Судно')
        names.append('Грузовик')
    elif imgType == 'CIFAR100':
        names.append('ВМ')
        names.append('Рыбы')
        names.append('Цветы')
        names.append('К-ры')
        names.append('ОФ')
        names.append('Дом')
        names.append('Мебель')
        names.append('Насек.')
        names.append('БХ')
        names.append('Постр.')
        names.append('Природа')
        names.append('Всеяд.')
        names.append('СХ.')
        names.append('Краб...')
        names.append('Люди')
        names.append('Репт.')
        names.append('МХ')
        names.append('Дер.')
        names.append('Вело...')
        names.append('Танк...')
    return names
#
# Загрузка MNIST или EMNIST
def load_data(allParams):
    num_classes = allParams[1]
    lossFunNum = allParams[3]
    # Флаг преобразования меток в категориальное представление
    categorical = False if lossFunNum == 10 else True # False для sparse_categorical_crossentropy
    img_rows = allParams[6]
    img_cols = allParams[7]
    pathToData = allParams[13]
    loadFromBin = allParams[14]
    show_img = allParams[19]
    saveData = allParams[20]
    nnType = allParams[24]
    useTestData = allParams[27]
    imgType = allParams[28]
    subtract_pixel_mean = allParams[33]
    # Загрузка данных
    # Читаем в списки imagesTrain, labelsTrain, imagesTest и labelsTest данные и метки
    # из ранее скаченных файлов - набора данных MNIST или EMNIST
    # Данные - это преставление числа (метки) на рисунке размера 28*28 пикселей
    # Метка - это число из диапазона [0, 9] или [0, 25]
    # В каждом пикселе представлен оттенок серого цвета. Он задается числом из диапазона [0, 255]
    if loadFromBin:
        imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData(pathToData)
    else:
        imagesTrain, labelsTrain, imagesTest, labelsTest = load_mnist(pathToData, saveData)
    #
    # Преобразование списков в одномерные массивы
    # Каждый элемент массива - это вектор размера 28*28 с целочисленными данными в диапазоне [0, 255]
    X_train = np.asarray(imagesTrain)
    y_train = np.asarray(labelsTrain)
    X_test = np.asarray(imagesTest)
    y_test = np.asarray(labelsTest)
    if imgType == 'EMNIST':
        y_train -= 1
        y_test -= 1
    if loadFromBin:
        X_train_shape_0 = int(X_train.shape[0] / (img_rows * img_cols)) # 60000 / 124800
        X_test_shape_0 = int(X_test.shape[0] / (img_rows * img_cols)) # 10000 / 20800
    else:
        X_train_shape_0 = X_train.shape[0]
        X_test_shape_0 = X_test.shape[0]
        ##print(X_train[0, :]) # MNIST. Напечатает 784 цифры, представляющих вариант рукописного числа 5
        ##print(y_train[0]) # MNIST. Напечатает: 5
    if useTestData:
        allParams.append(X_test_shape_0) # 39
        allParams.append(y_test) # Для predict в checkModel (40)
    else:
        allParams.append(X_train_shape_0) # 39
        allParams.append(y_train) # Для predict в checkModel (40)
    #
    # Меняем форму входных данных (обучающих и тестовых)
    # Реально имеем channels_last. Но можно изменить: K.set_image_data_format('channels_first')
    if nnType == 'conv' or show_img:
        if imgType == 'MNIST':
            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)
        elif imgType == 'EMNIST':
            X_train = X_train.reshape(X_train_shape_0, img_rows, img_cols, 1).transpose(0,2,1,3)
            X_test = X_test.reshape(X_test_shape_0, img_rows, img_cols, 1).transpose(0,2,1,3)
    elif nnType == 'mlp':
        im_size = img_rows * img_cols
        X_train = X_train.reshape(X_train_shape_0, im_size)
        X_test = X_test.reshape(X_test_shape_0, im_size)
    elif nnType == 'lstm':
        if imgType == 'MNIST':
            X_train = X_train.reshape(X_train_shape_0, img_rows, img_cols)
            X_test = X_test.reshape(X_test_shape_0, img_rows, img_cols)
        elif imgType == 'EMNIST':
            X_train = X_train.reshape(X_train_shape_0, img_rows, img_cols).transpose(0,2,1)
            X_test = X_test.reshape(X_test_shape_0, img_rows, img_cols).transpose(0,2,1)
    if show_img:
        if useTestData:
            print('Показываем примеры тестовых данных')
        else:
            print('Показываем примеры обучающих данных')
        print(X_train.shape) # (60'000, 28, 28, 1) или (12'4800, 28, 28, 1)
        print(y_train.shape) # (60'000,) или (124'800,)
        print(X_test.shape) # (10'000, 28, 28, 1) или (20'800, 28, 28, 1)
        print(y_test.shape) # (10'000,) или (20'800,)
        # Выводим 9 изображений обучающего или тестового набора
        names = makeNames(imgType)
        for i in range(9):
            plt.subplot(3, 3, i + 1)
            ind = y_test[i] if useTestData else y_train[i]
            img = X_test[i][0:img_rows, 0:img_rows, 0] if useTestData else X_train[i][0:img_rows, 0:img_rows, 0]
            # Вариант в случае EMNIST
            ##from scipy import ndimage
            ##img = img.reshape(img_rows, img_cols).transpose()
            ## или
            ##img = img.reshape(img_rows, img_cols)
            ##img = np.flipud(img)
            ##img = ndimage.rotate(img, -90)
            plt.imshow(img, cmap = plt.get_cmap('gray'))
            plt.title(names[ind])
            plt.axis('off')
        plt.subplots_adjust(hspace = 0.5) # wspace
        plt.show()
        sys.exit()
    # Преобразование целочисленных данных в float32; данные лежат в диапазоне [0.0, 255.0]
    X_train = X_train.astype('float32')
    X_test = X_test.astype('float32')
    ##print('Форма X_train:', X_train.shape) # MNIST. Напечатает: Форма X_train: (60000, 28, 28, 1)
    ##print(X_train.shape[0], 'обучающих образов') # MNIST. Напечатает: 60000 обучающих образов
    ##print(X_test.shape[0], 'тестовых образов') # MNIST. Напечатает: 10000 тестовых образов
    ##print('Форма y_train:', y_train.shape)
    ##print(X_train[0, :, :, 0]) # Напечатает массив размера 32*32
    #
    # Нормализация данных (приведение к диапазону [0.0, 1.0])
    print('Нормализация данных')
    X_train /= 255
    X_test /= 255
    #
    if subtract_pixel_mean:
        x_train_mean = np.mean(X_train, axis = 0)
        X_train -= x_train_mean
        X_test -= x_train_mean
    #
    # Преобразование в бинарное представление: метки - числа из диапазона [0, 9] в двоичный вектор размера num_classes
    # Так, в случае MNIST метка 5 (соответствует классу 6) будет преобразована в вектор [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
    #print(y_train[0]) # (MNIST) Напечатает: 5
    if categorical:
        print('Преобразуем массивы меток в категориальное представление')
        y_train = keras.utils.to_categorical(y_train, num_classes)
        y_test = keras.utils.to_categorical(y_test, num_classes)
        #print(y_train[0]) # (MNIST) Напечатает: [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
    return X_train, y_train, X_test, y_test
#
def load_cifar100(allParams):
    import pickle
    print('Загрузка CIFAR-100-данных')
    def unpickle(fileNm, N, img_rows, img_cols):
        with open(fileNm, 'rb') as fo:
            dict_file = pickle.load(fo, encoding = 'bytes')
        X = dict_file[b'data']
        Y = np.array(dict_file[b'fine_labels'])
        X = X.reshape(N, 3, img_cols, img_rows).transpose(0, 2, 3, 1)
        # Преобразование из диапазона 0-255 в диапазон 0.0-1.0
        X = X.astype('float32') / 255.0
        return X, Y
    num_classes = allParams[1]
    lossFunNum = allParams[3]
    # Флаг преобразования меток в категориальное представление
    categorical = False if lossFunNum == 10 else True # False для sparse_categorical_crossentropy
    img_rows = allParams[6]
    img_cols = allParams[7]
    pathToData = allParams[13]
    show_img = allParams[19]
    nnType = allParams[24]
    useTestData = allParams[27]
    imgType = allParams[28]
    subtract_pixel_mean = allParams[33]
    # Загрузка обучающих данных
    len_train = 50000
    im_size = img_rows * img_cols * 3 # 3072
    print('Загрузка и нормализация данных')
    X_train, y_train = unpickle(pathToData + 'cifar100/train', len_train, img_rows, img_cols)
    #
    # Загрузка тестовых данных
    len_test = 10000
    X_test, y_test = unpickle(pathToData + 'cifar100/test', len_test, img_rows, img_cols)
##    print(fileNm)
    if useTestData:
        allParams.append(len_test) # 39
        allParams.append(y_test) # Для predict в checkModel (40)
    else:
        allParams.append(len_train) # 39
        allParams.append(y_train) # Для predict в checkModel (40)
    ##print(X_test.shape) # Напечатает: (10000, 32, 32, 3), поскольку выполнили transpose(0,2,3,1)
    #
    if show_img:
        if useTestData:
            print('Показываем примеры тестовых данных')
        else:
            print('Показываем примеры обучающих данных')
        names = makeNames(imgType)
        len_show = len_test if useTestData else len_train
        # Выводим n_rows * n_in_row случайных изображений обучающего набора
        n_rows = 5
        n_in_row = 10
        fig, axs = plt.subplots(n_rows, n_in_row, figsize = (7, 4))
        title_font = {'fontname':'Arial', 'size':'9', 'color':'black'}
        for j in range(n_rows):
            for k in range(n_in_row):
                i = np.random.choice(range(len_show))
                ax1 = axs[j][k]
                ax1.set_axis_off()
                if useTestData:
                    ax1.imshow(X_test[i])
                    ax1.set_title(names[int(y_test[i] / 5)], **title_font)
                else:
                    ax1.imshow(X_train[i])
                    ax1.set_title(names[int(y_train[i] / 5)], **title_font)
        plt.subplots_adjust(hspace = 0.5, wspace = 0.0) # wspace
        plt.show()
        sys.exit()
    print('Форма X_train:', X_train.shape)
    print(X_train.shape[0], 'обучающих примеров')
    print(X_test.shape[0], 'проверочных примеров')
    print('Форма y_train:', y_train.shape)
    #
    if subtract_pixel_mean:
        x_train_mean = np.mean(X_train, axis = 0)
        X_train -= x_train_mean
        X_test -= x_train_mean
    if nnType == 'mlp':
        X_train = X_train.reshape(len_train, im_size)
        X_test = X_test.reshape(len_test, im_size)
    # Преобразование в бинарное представление
    if categorical:
        y_train = keras.utils.to_categorical(y_train, num_classes)
        y_test = keras.utils.to_categorical(y_test, num_classes)
    return X_train, y_train, X_test, y_test
#
def load_cifar10(allParams):
    import pickle
    def unpickle(file):
        with open(file, 'rb') as fo:
            dict_file = pickle.load(fo, encoding = 'bytes')
        return dict_file
    print('Загрузка CIFAR-10-данных')
    num_classes = allParams[1]
    lossFunNum = allParams[3]
    # Флаг преобразования меток в категориальное представление
    categorical = False if lossFunNum == 10 else True # False для sparse_categorical_crossentropy
    img_rows = allParams[6]
    img_cols = allParams[7]
    pathToData = allParams[13]
    show_img = allParams[19]
    nnType = allParams[24]
    useTestData = allParams[27]
    imgType = allParams[28]
    subtract_pixel_mean = allParams[33]
    fileNm = pathToData + 'cifar-10-batches-py/data_batch_'
    # Загрузка обучающих данных
    len_train = 50000
    im_size = img_rows * img_cols * 3 # 3072
    X_train = np.zeros((len_train, im_size), dtype = 'uint8')
    y_train = np.zeros(len_train, dtype = 'uint8')
    m = -1
    for i in range(5):
        dict_i = unpickle(fileNm + str(i + 1))
##        print(fileNm + str(i + 1))
        X = dict_i[b'data']
        Y = dict_i[b'labels']
        Y = np.array(Y)
        for k in range(10000):
            m += 1
            X_train[m, :] = X[k, :]
            y_train[m] = Y[k]
    X_train = X_train.reshape(len_train, 3, img_rows, img_cols).transpose(0,2,3,1) # .astype('uint8')
    #
    # Загрузка тестовых данных
    len_test = 10000
    fileNm = pathToData + 'cifar-10-batches-py/test_batch'
    dict_i = unpickle(fileNm)
    X_test = dict_i[b'data']
    y_test = dict_i[b'labels']
    X_test = X_test.reshape(len_test, 3, img_rows, img_cols).transpose(0,2,3,1) # .astype('uint8')
    y_test = np.array(y_test)
    if useTestData:
        allParams.append(len_test) # 39
        allParams.append(y_test) # Для predict в checkModel (40)
    else:
        allParams.append(len_train) # 39
        allParams.append(y_train) # Для predict в checkModel (40)
    ##print(X_test.shape) # Напечатает: (10000, 32, 32, 3), поскольку выполнили transpose(0,2,3,1)
    #
    if show_img:
        if useTestData:
            print('Показываем примеры тестовых данных')
        else:
            print('Показываем примеры обучающих данных')
        names = makeNames(imgType)
        len_show = len_test if useTestData else len_train
        # Выводим n_rows * n_in_row случайных изображений обучающего набора
        n_rows = 5
        n_in_row = 10
        fig, axs = plt.subplots(n_rows, n_in_row, figsize = (7, 4))
        title_font = {'fontname':'Arial', 'size':'9', 'color':'black'}
        for j in range(n_rows):
            for k in range(n_in_row):
                i = np.random.choice(range(len_show))
                ax1 = axs[j][k]
                ax1.set_axis_off()
                if useTestData:
                    ax1.imshow(X_test[i])
                    ax1.set_title(names[y_test[i]], **title_font)
                else:
                    ax1.imshow(X_train[i])
                    ax1.set_title(names[y_train[i]], **title_font)
        plt.subplots_adjust(hspace = 0.5, wspace = 0.0) # wspace
        plt.show()
        sys.exit()
    # Преобразование из диапазона 0-255 в диапазон 0.0-1.0
    X_train = X_train.astype('float32')
    X_test = X_test.astype('float32')
    print('Нормализация данных')
    X_train /= 255.0
    X_test /= 255.0
    #
    print('Форма X_train:', X_train.shape)
    print(X_train.shape[0], 'обучающих примеров')
    print(X_test.shape[0], 'проверочных примеров')
    print('Форма y_train:', y_train.shape)
    #
    if subtract_pixel_mean:
        x_train_mean = np.mean(X_train, axis = 0)
        X_train -= x_train_mean
        X_test -= x_train_mean
    if nnType == 'mlp':
        X_train = X_train.reshape(len_train, im_size)
        X_test = X_test.reshape(len_test, im_size)
    # Преобразование в бинарное представление
    if categorical:
        print('Преобразуем массивы меток в категориальное представление')
        y_train = keras.utils.to_categorical(y_train, num_classes)
        y_test = keras.utils.to_categorical(y_test, num_classes)
    return X_train, y_train, X_test, y_test

Приложение 5. Программа обработки историй обучения

## Список обучения - список моделей НС, обучаемых на заданном наборе данных
## Серия - реализация циклов обучения для всего списка обучения
## Цикл - обучение модели со всеми функциями потерь
## Эпизод - обучение модели с одной функцией потерь
## В каждом цикле 16 эпизодов
#
import os
import sys # Для sys.exit()
import numpy as np
import matplotlib.pyplot as plt
#
# Число анализируемых функциий потерь
n_funs = 16
#
# Номер набора данных
dataSetNum = 1 # 1 - MNIST; 2 - EMNIST; 3 - CIFAR10
#
# showBadFuns = 0 - поиск лучших функций по заданному показателю
# showBadFuns = 1 - вывод имен функций с малым числом эпох
# showBadFuns = 2 - вывод имен функций с неполным числом метрик
# showBadFuns = 3 - вывод имен функций, истории которых имеют разную длину
# showBadFuns = 4 - находим среднее число эпох в историях обучения
# showBadFuns = 5 - сравнение потерь mse и kld и cce и kld. Для обоснования функций mk и ck
# Вычисляем суммы потерь и большую сумму делим на меньшую
# Для каждой функции должно быть сохранено 4 txt-файла, например (расширение .txt опущено):
# acc_MNIST_cce_Adam_c7, loss_MNIST_cce_Adam_c7, val_acc_MNIST_cce_Adam_c7, val_loss_MNIST_cce_Adam_c7
showBadFuns = 0
#
# sva = 0 - поиск лучших решений по критерию corr_img (err_img = all_img - corr_img)
# sva = 1 - поиск лучших решений по критерию val_acc
# sva = 2 - поиск лучших решений по критерию acc
sva = 1
if sva == 0: crit_name = 'err_img'
elif sva == 1: crit_name = 'val_acc'
elif sva == 2: crit_name = 'acc'
#
# Вычислять средневзвешенную эффективность всех функций потерь (случай one_dir = 0 или 1)
# Флаг поиска средневзвешенной оценки эффективности ФП на СЛР (False) или по все серии обучения (True)
#
mean_eff_all = False # True False
#
# Флаг присутствия лучшей НС в списке обучения
# Для оценки возможности прогнозировать эффективные НС для вновь обучаемой НС
has_best_nn = True # True False
#
# Флаг работы с одной директорией
# В директории истории обучения одной модели НС с 16-ю функциями потерь
# 0 - все директории одновременно (поиск лучших функций и моделей)
# 1 - одна директория
# 2 - все директории по одной (поиск моделей с одинаковыми эффективными функциями потерь)
#
one_dir = 2 # 0 1 2
#
if one_dir == 2: mean_eff_all = True
#
# Погрешность эксперимента. Используется для формировании списков эффективных ФП при mean_eff_all = False
# и равнозначных ФП при one_dir = 1. Имеет значение при sva = 1
# low_level_1 - нижняя граница критерия. Случай sva = 1.
# Эта граница берется после запуска с mean_eff_all = True,
# равная val_acc_СО выведенного списка эффективных функций потерь по критерию val_acc
# low_level_1 меняется после изменения списка обучения
if dataSetNum == 1:
    Delta = 0.024
    low_level_1 = 0.9952 if has_best_nn else 0.995
    best_nn = 'w_5'
elif dataSetNum == 2:
    Delta = 0.048
    low_level_1 = 0.9488 if has_best_nn else 0.9482
    best_nn = 'w_3'
elif dataSetNum == 3:
    Delta = 0.217
    low_level_1 = 0.7738 if has_best_nn else 0.7599
    best_nn = 'w_1'
#
w_dir = best_nn # Имя папки для случая one_dir = 1
##w_dir = '2w_28'
#
# Флаг поиска двух следующих показателей на обучающей выборке при заданном критерии:
# - число совпадений лучших решений с решением с наименьшими потерями (в %)
# - среднее относительное отклонение по потерям между лучшим решением и решением с наименьшими потерями (в %)
findLossesDif = False # True False
#
# Флаг использования одной ФП
oneFun = False # True False
oneFunName = 'lc' # Имя ФП для случая oneFun = True
if oneFun:
    mean_eff_all = True
    print('Обработка историй обучения с одной функцией потерь:', oneFunName)
#
# Дополнительно при mean_eff_all = False и oneFun = False дается описание следующих решений:
# - решения с наибольшей разницей между номерами эпох обновления максимума заданного критерия
# - решения с наибольшей разницей между значениями двух последних обновлений максимума заданного критерия
#
# Режим one_dir = 2 (сканирование директорий по одной) и mean_eff_all = False
# Используем параметр near
# Вывод пар моделей с совпадающими на near% списками эффективных функций потерь
# Выполняется, если near > 0
# Коэффициент близости списков эффективных функций потерь моделей НС
near = 0 # 0.85
if near > 0:
    if one_dir < 2:
        print('Плохое значение one_dir. Должно быть 2'); sys.exit()
    if mean_eff_all:
        print('Плохое значение mean_eff_all. Должно быть False'); sys.exit()
#
# Показывать лучшие решения:
# show_all_best = True - для каждой модели и ФП выводится описание эпохи, отвечающей лучшему решению по заданному критерию
# Так, в случае 20 моделей будет выведено описание 320 эпох
# Если show_all_best = False, то показывается первое лучшее решение
# Если show_all_best = True, то поиск характеристик историй обучения и эффективных функций потерь не выполняется
show_all_best = True # True False (True лучше не использовать, а если использовать, то при one_dir = 1)
if one_dir != 1 and not oneFun: show_all_best = False
#
# Печатать описание эпох
# print_epoch_descr – флаг вывода описаний эпох. Использовать при one_dir = 1, иначе будет слишком много печати): для каждой модели и ФП выводится описание эпохи, отвечающей лучшему решению по заданному критерию. Так, в случае 20 моделей будет выведено описание 320 эпох.<br>
print_epoch_descr = False # True False (Задавать True, если one_dir = 1, иначе будет слишком много печати)
if not show_all_best: print_epoch_descr = True
#
# Показывать детали при выводе лучших решений
print_details = False # True False (True лучше не использовать, иначе будет слишком много печати)
#
# Флаг вывода списка эффективных функций потерь
print_eff_funs_list = True # True False
if one_dir == 2: print_eff_funs_list = False
#
# Флаг вывода списка эффективных моделей. Работает только, когда one_dir = 0
print_eff_models_list = True # True False
if one_dir == 1: print_eff_models_list = False
#
# Вывод диаграмм
# Флаг вывода диаграммы средневзвешенной эффективности ФП
# False отменяет вывод диаграмм эффективности ФП. Введен, чтобы не менять значения showFreq, showDiag2 и showFreqDiag
lossFunsEffDiag = True # True False
#
showDiag2 = bestResultsDiag = showFreq = showFreqDiag = False
if lossFunsEffDiag:
    if one_dir == 2:
        # Режим one_dir = 2 (сканирование директорий по одной)
        # Показывать диаграмму частоты появления функций в списках эффективных функций моделей НС
        # Диаграмма выводится, если showFreq = True and lossFunsEffDiag = True
        showFreq = True # True False (случай one_dir = 2)
        mean_eff_all = False
        if showFreq and oneFun: print('Плохое значение oneFun: должно быть равным False'); sys.exit()
    elif one_dir == 0:
        # Режим one_dir = 0
        # Показывать частотную диаграмму эффективности функций потерь (строится по всем директориям)
        showFreqDiag = False # True False (случай one_dir = 0)
        #
        # Показывать вторую диаграмму эффективности функций потерь
        # Строится по одной директории, если one_dir = 1
        # Строится по всем директориям и всем функциям потерь, если one_dir = 0 и mean_eff_all = True
        showDiag2 = True # True False
        if showFreqDiag and showDiag2: print('Нужно определиться с видом диаграммы'); sys.exit()
    elif one_dir == 1:
        showDiag2 = True # True False
        if (showFreq or showFreqDiag): print('Плохое значение one_dir: должно быть равным 0 или 2'); sys.exit()
    if (showFreq or showFreqDiag) and mean_eff_all: print('Плохое значение mean_eff_all: должно быть равным False'); sys.exit()
else:
    # Флаг вывода диаграммы лучших решений
    bestResultsDiag = False # True False
    n_to_show = 10 # Число выводимых решений
#
# Вывод графиков
# Выводить график точности или потерь (строится для лучшего решения, если res_number = 0)
plot_acc_loss = False # True False
#
if bestResultsDiag and plot_acc_loss: print('Плохое значение plot_acc_loss: должно быть False'); sys.exit()
if bestResultsDiag and oneFun: print('Плохое значение oneFun: должно быть False'); sys.exit()
#
# Флаг вывода графика точности (is_acc = True) или потерь (is_acc = False)
is_acc = False # True False
#
# Номер отображаемого решения. Лучшее решение имеет номер 0
res_number = 0
#
if plot_acc_loss: showFreq = showFreqDiag = showDiag2 = False
#
# Дополнительные характеристики истории обучения
plot_history_values = 0
if not show_all_best and one_dir < 2 and mean_eff_all:
    # Выводить диаграмму расстояний между эпохами последовательного обновления максимума критерия
    # и диаграмму разницы между двумя последними обновлениями максимума критерия
    # 0 - не выводить, 1 - диаграмма расстояний; 2 - диаграмма разницы
    # 3 - диаграмма числа обновлений максимума критерия
    plot_history_values = 0
#
conv_list = [
    [ '1', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL30'],    [ '2', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL16'],
    [ '3', 'CR32-P-CR64-P-F-0.35-DR1024-0.2-DL52'],    [ '4', 'CR20-P-CR30-P-F-DR600-DL30'],
    [ '5', 'CR20-P-CR30-P-F-0.2-DR100'],             [ '6', 'CR20-P-CR30-P-F-DR600'],
    [ '7', 'CR10-P-CR15-P-F-0.2-DR300'],             [ '8', 'CR10-P-CR15-P-F-0.25-DR300-DL30'],
    [ '9', 'CR20-P-CR30-P-F-DR600-DL16'],             ['10', 'CR20-P-CR30-P-F-DR600-DL52'],
    ['11', 'CR20-P-CR30-P-F-0.25-DR600-DL16'],         ['12', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL16'],
    ['13', 'CR20-P-CR30-P-F-0.25-DR600-DL30'],         ['14', 'CR20-P-CR30-P-F-0.25-DR600-0.2-DL30'],
    ['15', 'CR32-P-CR64-P-F-0.25-DR800-DL16'],         ['16', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL16'],
    ['17', 'CR32-P-CR64-P-F-0.25-DR800-DL30'],         ['18', 'CR32-P-CR64-P-F-0.25-DR800-0.2-DL30'],
    ['19', 'CR10-P-CR15-P-F-0.25-DR300'],             ['20', 'CR10-P-CR15-P-F-0.3-DR300'],
    ['21', 'CR10-P-CR15-P-F-0.35-DR300'],             ['22', 'CR10-P-CR15-P-F-0.3-DR300-0.3'],
    ['23', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL90-0.2'], ['24', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL52-0.2'],
    ['25', 'CR20-P-CR30-P-F-DR600-DL60'],             ['26', 'CR20-P-CR30-P-F-0.3-DR600-DL60'],
    ['27', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60'],     ['28', 'CR20-P-CR30-P-F-0.3-DR600-0.2-DL60-0.2'],
    ['29', 'CR10-P-CR15-P-F-0.3-DR300-0.25-DL50-0.2'], ['30', 'CR20-P-0.25-CR30-CR30-P-0.25-F-DR512-0.5'],
    ['31', 'CR15-P-CR10-P-F-0.4-DR300'],
    ['32', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.35-DR128-0.25'],
    ['33', 'CR16-CR16-P-0.2-CR32-CR32-P-0.25-F-0.25-DR512-0.3-DL32-0.2'],
    ['34', 'C32-BN-R-C32-BN-R-P-0.25-C64-BN-R-C64-BN-R-P-0.25-F-D512-BN-R-0.5'],
    ['35', 'BN-CR32-CR32-P-0.25-BN-CR64-CR64-P-0.25-F-DR512-0.5-DR128-0.25'],
    ['36', 'ResNet20v1'],
    ['37', '12bn: C20-BN-R-P-C30-BN-R-P-F-0.25-D600-BN-R-0.2-D16-BN-L'],
    ['38', '28bn: C20-BN-R-P-C30-BN-R-P-F-0.3-D600-BN-R-0.2-D60-BN-L-0.2']]
# Перечисляются значения числа параметров НС для MNIST, EMNIST и CIFAR-10
conv_size_list = [
    [ "1", "3'276'724", "3'277'220", "4'245'780"], [ "2", "3'262'234", "3'262'506", "4'231'290"],
    [ "3", "3'299'494", "3'300'342", "4'268'550"], [ "4", "910'910", "911'406", "1'176'930"],
    [ "5", "158'080", "159'696", "199'100"],        [ "6", "898'580", "908'196", "1'164'600"],
    [ "7", "226'395", "231'211", "292'955"],        [ "8", "232'725", "233'221", " 299'285"],
    [ "9", "902'356", "902'628", "1'168'376"],     ["10", "924'352", "925'200", "1'190'372"],
    ["11", "902'356", "902'628", "1'168'376"],     ["12", "902'356", "902'628", "1'168'376"],
    ["13", "910'910", "911'406", "1'176'930"],     ["14", "910'910", "911'406", "1'176'930"],
    ["15", "2'555'962", "2'556'234", "3'309'978"], ["16", "2'555'962", "2'556'234", "3'309'978"],
    ["17", "2'567'316", "2'567'812", "3'321'332"], ["18", "2'567'316", "2'567'812", "3'321'332"],
    ["19", "226'395", "231'211", "292'955"],        ["20", "226'395", "231'211", "292'955"],
    ["21", "226'395", "231'211", "292'955"],        ["22", "226'395", "231'211", "292'955"],
    ["23", "947'570", "949'026", "1'213'590"],     ["24", "947'570", "949'026", "1'213'590"],
    ["25", "929'240", "930'216", "1'195'260"],     ["26", "929'240", "930'216", "1'195'260"],
    ["27", "929'240", "930'216", "1'195'260"],     ["28", "929'240", "930'216", "1'195'260"],
    ["29", "238'945", "239'761", "305'505"],        ["30", "772'042", "780'250", "1'002'802"],
    ["31", "152'975", "157'971", "197'090"],        ["32", "899'306", "901'370", "1'132'698"],
    ["33", "849'098", "849'626", "1'082'490"],     ["34", "1'728'074", "1'736'282", "2'169'770"],
    ["35", "1'788'556", "1'790'620", "2'230'256"], ["36", "272'778", "273'818", "273'066"],
    ["37", "1'807'376", "1'807'920", "3'508'044"], ["38", "1'861'320", "1'863'272", "3'588'960"]]
#
# Сравнение потерь mse и kld. Для обоснования функции mk
# Вычисляем суммы потерь и большую сумму делим на меньшую
def compare_for_mk(imgType, dirs, pathToData):
    loss_mse = loss_kld = loss_cce = loss_kld2 = 0
    prf = 'loss_' + imgType
    pref = prf + '_mse_A'
    pref2 = prf + '_kld_A'
    pref3 = prf + '_cce_A'
    len_pref = len(pref)
    for dr in dirs:
        if one_dir == 1 and dr != w_dir: continue
        print(dr)
        p = pathToData + dr + '/'
        files = os.listdir(p)
        k = 0
        vl_list, vl_list2 = [], []
        for fn in files:
            fn_pref = fn[0:len_pref]
            if fn_pref == pref:
                print(fn)
                p_fn = p + fn
                vl_list = readFile(p_fn)
                k += 1
            if fn_pref == pref2:
                print(fn)
                p_fn = p + fn
                vl_list2 = readFile(p_fn)
                k += 1
            if fn_pref == pref3:
                print(fn)
                p_fn = p + fn
                vl_list3 = readFile(p_fn)
                k += 1
            if k == 3: break
        len_list = min(len(vl_list), len(vl_list2))
        for k in range(len_list):
            loss_mse += vl_list[k]
            loss_kld += vl_list2[k]
        len_list = min(len(vl_list2), len(vl_list3))
        for k in range(len_list):
            loss_kld2 += vl_list2[k]
            loss_cce += vl_list3[k]
        if one_dir == 1:
            print('Число складываемых значений потерь:', len_list)
            break
    coef_mk = loss_kld / loss_mse
    print('Коэффициент превышения потерь kld над потерями mse:', coef_mk)
    # MNIST: coef_mk = 17.22
    # EMNIST: coef_mk = 45.35
    # CIFAR10: coef_mk = 19.8
    coef_ck = loss_cce / loss_kld2
    print('Коэффициент превышения потерь cce над потерями kld:', coef_ck)
    # MNIST: coef_ck = 1.003
    # EMNIST: coef_ck = 1.004
    # CIFAR10: coef_ck = 1.01
#
# Среднее число эпох в историях обучения
def avgEpchsNumber(sva, funs, dirs, pref_list, ks, pathToData):
    pref = pref_list[2]
    files_amt = epoch_amt = 0
    for dr in dirs:
        #print(dr)
        p = pathToData + dr + '/'
        files = os.listdir(p)
        for fn in files:
            fun = find_fun(sva, fn, funs, pref, ks)
            if fun == 'skip': continue
            p_fn = p + fn
            vl_list = readFile(p_fn)
            epoch_n = len(vl_list)
            if epoch_n < 20: print('Мало эпох:', p_fn); continue
            files_amt += 1
            epoch_amt += epoch_n
    print('Всего эпох:', epoch_amt)
    print('Всего файлов:', files_amt)
    print('Среднее число эпох:', round(epoch_amt / files_amt, 0))
#
# Поиск двух следующих показателей на обучающей выборке при заданном критерии:
# - число совпадений лучших решений с решением с наименьшими потерями
# - среднего относительного отклонения по потерям между лучшим решением и решением с наименьшими потерями
def find_losses_dif(all_crit_list, all_loss_list):
    k = -1
    epoch_losses_same, losses_dif = 0, 0.0
    for crit_list in all_crit_list:
        k += 1
        fun = crit_list[9]
        epoch_crit = crit_list[1]
        loss_crit = crit_list[3]
        loss_list = all_loss_list[k]
        epoch_loss = loss_list[1]
        loss_loss = loss_list[3]
        dif = loss_crit - loss_loss
        if epoch_crit == epoch_loss or dif == 0:
            epoch_losses_same += 1
        else:
            losses_dif += (dif / loss_loss)
##            print('dr =', crit_list[8], 'fun = ', fun)
##            print('epoch_crit =', epoch_crit, 'loss_crit =', loss_crit)
##            print('epoch_loss =', epoch_loss, 'loss_loss =', loss_loss)
            if dif < 0:
                print('Error in find_losses_dif. epoch_crit =', epoch_crit, 'loss_crit =', loss_crit,
                     'epoch_loss =', epoch_loss, 'loss_loss =', loss_loss, 'dr =', crit_list[8], 'fun = ', fun)
                sys.exit()
    k += 1
    if one_dir == 2:
        return epoch_losses_same, losses_dif, k
    else:
        epoch_losses_same = 100.0 * epoch_losses_same / k
        losses_dif = 100.0 * losses_dif / k
        print('\nВсего решений:', k)
        print('Число совпадений лучших решений с решением с наименьшими потерями, %:', epoch_losses_same)
        print('Среднее относительное отклонения по потерям между лучшими решениями и решениями с наименьшими потерями loss, %:', losses_dif)
#
def scanDirsOneByOne(dirs):
    dirs_loss_funs_list = []
    all_eff_funs_list = []
    epoch_losses_same_all, losses_dif_all, sol_all = 0, 0.0, 0 # sol_all - всего решений
    # Элемент списка dr_loss_funs_list - это список следующего вида:
    # [модель, список эффективных функций потерь модели]
    for dr in dirs:
        if print_eff_funs_list: print('Модель:', dr)
        all_crit_list = find_max_in_dirs(sva, dataSetNum, [dr], pathToData, pref_list, ks)
        if findLossesDif:
            all_loss_list = findBestLosses(sva, dataSetNum, pref_list[1], all_crit_list)
            epoch_losses_same, losses_dif, sol = find_losses_dif(all_crit_list, all_loss_list)
            epoch_losses_same_all += epoch_losses_same
            losses_dif_all += losses_dif
            sol_all += sol
        dr_loss_funs_list = bestLossFuns(sva, showFreqDiag, showDiag2, plot_acc_loss, print_eff_funs_list,
                                         imgType, res_number, all_crit_list)
        temp_list = []
        for m in dr_loss_funs_list:
            fun = m[0]
            temp_list.append(fun)
            if fun not in all_eff_funs_list: all_eff_funs_list.append(fun)
        temp_list.sort()
        dirs_loss_funs_list.append([dr, temp_list])
    if findLossesDif:
        epoch_losses_same_all = 100.0 * epoch_losses_same_all / sol_all
        losses_dif_all = 100.0 * losses_dif_all / sol_all
        print('Всего решений:', sol_all)
        print('Число совпадений лучших решений с решением с наименьшими потерями, %:', epoch_losses_same_all)
        print('Среднее относительное отклонения по потерям между лучшими решениями и решениями с наименьшими потерями loss, %:', losses_dif_all)
    print('Эффективные функции потерь моделей НС')
    for m in dirs_loss_funs_list: print(m)
    all_eff_funs_list.sort()
    # Частота появления функций потерь в списках эффективных функций потерь моделей НС
    freq_list = []
    for fun in all_eff_funs_list: freq_list.append(0)
    k = -1
    for fun in all_eff_funs_list:
        k += 1
        for m in dirs_loss_funs_list:
            eff_funs_list = m[1]
            if fun in eff_funs_list: freq_list[k] += 1
    k = -1
    for fun in all_eff_funs_list:
        k += 1
        all_eff_funs_list[k] = [fun, freq_list[k]]
    all_eff_funs_list.sort(key = lambda r: r[1], reverse = True)
    print('Частота появления функций в списках эффективных функций моделей НС')
    print(all_eff_funs_list)
    if near > 0:
        # Пары моделей с совпадающими на near% списками эффективных функций потерь
        print('Коэффициент близости списков эффективных функций потерь моделей НС:', near)
        n = 0
        dirLen = len(dirs_loss_funs_list)
        all_eqw = []
        included_list = []
        for m in dirs_loss_funs_list:
            dr = m[0]
            eff_funs_list = m[1]
            effLen = len(eff_funs_list)
            n += 1
            for k in range(n, dirLen):
                m2 = dirs_loss_funs_list[k]
                dr2 = m2[0]
                eff_funs_list2 = m2[1]
                n_eq = 0 # Число одинаковых функций потерь в паре списков
                for fun in eff_funs_list:
                    for fun2 in eff_funs_list2:
                        if fun == fun2: n_eq += 1
                if n_eq >= 0.5 * near * (effLen + len(eff_funs_list2)):
                    all_eqw.append([dr, dr2, eff_funs_list, eff_funs_list2])
                    if not dr in included_list: included_list.append(dr)
                    if not dr2 in included_list: included_list.append(dr2)
        def modelInfo(m, i):
            nn = m[i]
            if nn == 'res_net':
                k = 35
                c = '1' # Число ветвей в сборке
            else:
                dr = nn.split('_')
                k = int(dr[1]) - 1
                nn = conv_list[k][1] # Обозначение модели НС
                c = dr[0][0:1]
            ns = conv_size_list[k][dataSetNum] # Число обучаемых параметров
            return nn, ns, c
        print('Пары моделей с близкими списками эффективных функций потерь')
        k = 0
        for m in all_eqw:
            nn, ns, c = modelInfo(m, 0)
            nn2, ns2, c2 = modelInfo(m, 1)
            k += 1
            print(k)
            print(m[0:2])
            print(m[2:3])
            print(m[3:4])
            print(nn, '/', ns, ('' if c == 'w' else ('* ' + c)))
            print(nn2, '/', ns2, ('' if c2 == 'w' else ('* ' + c2)))
        no_list = []
        for dr in all_dirs:
            if not dr in included_list: no_list.append(dr)
        print('Список моделей, не имеющих моделей с близким списком эффективных функций потерь:'),
        print(no_list)
        print('Длина списка:', len(no_list))
        print('Всего моделей:', len(all_dirs))
    if showFreq and lossFunsEffDiag:
        print('Строим диаграмму частоты появления функций в списках эффективных функций моделей НС')
        plotLossFunsEffDiag(True, imgType, crit_name, all_eff_funs_list, len(all_dirs), (7, 2.4), ' / ')
#
# Эффективные функции потерь
def bestLossFuns(sva, showFreqDiag, showDiag2, plot_acc_loss, print_eff_funs_list, imgType, res_number, all_crit_list):
    crit_val = 0
    all_loss_funs_list = []
    for crit_list in all_crit_list:
        loss_fun = crit_list[9]
        if sva == 0: crit_val = all_img - crit_list[6]
        elif sva == 1: crit_val = crit_list[4]
        elif sva == 2: crit_val = crit_list[2]
        not_in_list = True
        k = - 1
        for loss_funs_list in all_loss_funs_list:
            k += 1
            if loss_funs_list[0] == loss_fun:
                all_loss_funs_list[k][1] += 1
                all_loss_funs_list[k][2] += crit_val
                crit_min = all_loss_funs_list[k][3]
                crit_max = all_loss_funs_list[k][4]
                if crit_val < crit_max: all_loss_funs_list[k][3] = crit_val
                if crit_val > crit_max: all_loss_funs_list[k][4] = crit_val
                not_in_list = False
                break
        # Элементом спискк all_loss_funs_list является список, содержащий имя ФП,
        # число присутствий в списке лучших решенийб суммарное, минимальное и максимальное значения критерия
        if not_in_list: all_loss_funs_list.append([loss_fun, 1, crit_val, crit_val, crit_val])
    k = -1
    for loss_funs_list in all_loss_funs_list:
        k += 1
        # Находим среднее значение критерия
        all_loss_funs_list[k][2] /= all_loss_funs_list[k][1]
        # rnd - число десятичных знаков
        if sva == 0:
            rnd = 0 if all_loss_funs_list[k][1] == 1 else 4
        elif sva == 1: rnd = 2
        elif sva == 2: rnd = 4
        all_loss_funs_list[k][2] = round((100 if sva > 0 else 1) * all_loss_funs_list[k][2], rnd)
    if sva == 0:
        # Сортируем по убыванию частоты и возрастанию err_img
        all_loss_funs_list.sort(key = lambda r: (-r[1], r[2]))
    else:
        # Сортируем по убыванию частоты и acc или val_acc
        all_loss_funs_list.sort(key = lambda r: (-r[1], -r[2]))
    if print_eff_funs_list:
        print('\nСписок эффективных функций потерь по критерию', crit_name + ':')
        print('Элемент списка содержит: имя ФП, число присутствий в СЛР, среднее, минимальное и максимальное значение критерия')
        print('В случае sva = 1 и one_dir = 1 в конце строки выводится\nэпоха лучшего решения по val_acc и число эпох')
        print('Нижняя граница критерия:', low_level[sva])
        k = 0
        n_sol = 0 # Всего потенциально лучших решений
        max_n = all_loss_funs_list[0][1] # Число присутствий лучшей ФП в списке лучших решений
        lf_lst = []
        lf_lst_crit_val = []
        lf_lst_crit_val_2 = []
        for loss_funs_list in all_loss_funs_list:
            k += 1
            if sva == 1 and one_dir == 1:
                print(k, loss_funs_list, '-', (all_crit_list[k - 1][1] + 1), '-', all_crit_list[k - 1][0])
            else:
                if sva == 1:
                    ls_fun = loss_funs_list[0]
                    val_acc_avg = loss_funs_list[2]
                    val_acc_min = round(100 * loss_funs_list[3], 2)
                    val_acc_max = round(100 * loss_funs_list[4], 2)
                    if mean_eff_all:
                        print(ls_fun, val_acc_avg, val_acc_min, '-', val_acc_max)
                    else:
                        print(k, ls_fun, loss_funs_list[1], val_acc_avg, val_acc_min, val_acc_max)
                else:
                    print(k, loss_funs_list)
            if loss_funs_list[1] == max_n:
                lf_lst.append(loss_funs_list[0])
                lf_lst_crit_val.append(loss_funs_list[2])
                lf_lst_crit_val_2.append([loss_funs_list[0], loss_funs_list[2]])
            # Вывод для сайта
##            print(loss_funs_list[0:3])
            n_sol += loss_funs_list[1]
        if sva == 1:
            print('Среднее значение критерия по всему списку:', sum(lf_lst_crit_val) / len(lf_lst_crit_val))
            max_vl = lf_lst_crit_val[0]
            n_out = 0
            for vl in lf_lst_crit_val:
                if abs(max_vl - vl) <= Delta: n_out += 1
            print('Всего эффективных ФП:', n_out, '. Delta =', Delta, '. max_vl =', max_vl)
            if not has_best_nn:
                print('Лучшей НС нет в списке обучения:', best_nn)
            print(lf_lst[0:n_out])
            print(lf_lst_crit_val[0:n_out])
            print(lf_lst_crit_val_2)
        print('Можно включить вывод для сайта')
        if not mean_eff_all: print('Всего потенциально лучших решений:', n_sol)
    #
    d = ' / '
    # Диаграммы эффективности функций потерь
    if (showFreqDiag or showDiag2) and one_dir < 2 and lossFunsEffDiag:
        plotLossFunsEffDiag(showFreqDiag, imgType, crit_name, all_loss_funs_list,
                 dirs[0] if one_dir == 1 else len(dirs), (7, 2.4), d)
    # Графики истории обучения. Берем лучшее решение
    if plot_acc_loss:
        plot_hist(is_acc, sva, all_crit_list, pref_list, imgType, crit_name, d, res_number)
    return all_loss_funs_list
#
def showBestSolutions(sva, show_all_best, all_crit_list, all_img):
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    # Если sva = 1 и show_all_best, то формируем список acc_list_for_val_all, содержащий acc для лучших решений по val_acc
    # Используется для поиска пересечения списков неверно классифицируемых изображений на обучающих данных
    acc_list_for_val_all = []
    print('Лучшие решения:' if show_all_best else 'Первое лучшее решение:')
    k = -1
    for crit_list in all_crit_list:
        acc_list = []
        val_acc_list = []
        err_img_list = []
        k += 1
        epoch_all, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(crit_list, all_img)
        if sva == 1 and show_all_best:
            acc_list_for_val_all.append([crit_list[8], crit_list[9], 100 * crit_list[2], all_img - crit_list[6]])
        print(str(k + 1) + '.')
        print(crit_list[7:10])
        print('Всего эпох:', epoch_all)
        print('Описание эпох:')
        ttl = 'Эпоха лучшего решения по '
        if sva == 1:
            ttl += 'val_acc:' # точности на тестовой выборке
        elif sva == 2:
            ttl += 'acc:' # точности на обучающей выборке
        else:
            ttl += 'err_img:' # числу неверно классифицированных изображений
        best_list = say_ep(ttl, 0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        if sva == 1 or sva == 0:
            ttl = 'Эпоха наибольшей точности acc:'
            epoch_0, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(all_acc_list[k], all_img)
            acc_list = say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        if sva == 2 or sva == 0:
            ttl = 'Эпоха наибольшей точности val_acc:'
            epoch_0, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(all_val_acc_list[k], all_img)
            val_acc_list = say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        ttl = 'Эпоха наименьших loss-потерь:'
        epoch_0, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(all_loss_list[k], all_img)
        loss_list = say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        ttl = 'Эпоха наименьших val_loss-потерь:'
        epoch_0, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(all_val_loss_list[k], all_img)
        val_loss_list = say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        if sva > 0:
            # Эпоха с наименьшим числом неверно классифицированных изображений
            ttl = 'Эпоха с наименьшим err_img:'
            epoch_0, epoch, acc, val_acc, loss, val_loss, err_img = descr_ep(all_sum_list[k], all_img)
            err_img_list = say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details)
        print(best_list)
        if len(acc_list) > 0: print(acc_list)
        if len(val_acc_list) > 0: print(val_acc_list)
        print(loss_list)
        print(val_loss_list)
        if len(err_img_list) > 0: print(err_img_list)
        if not show_all_best: break
    if sva == 1 and show_all_best:
        print('Значение критериев acc и err_img для лучших решений по val_acc')
        acc_list_for_val_all.sort(key = lambda r:r[2], reverse = True)
        for acc_list in acc_list_for_val_all: print(acc_list)
#
def find_history_values(sva, dataSetNum, all_img, funs, all_crit_list, plot_history_values):
    dist_max = 0 # Максимальное расстояние между двумя последовательными обновлениями максимумов критерия
    dif_max = 0 # Максимальная разница между двумя последовательными максимумами критерия
    fun_dist_max = fun_dif_max = dr_dist_max = dr_dif_max = ''
    v_dist_max = v_dist_max2 = crit_dist_max = k_dist_max = k_dist_max2 = epoch_dist_all = epoch_dist = 0
    k_dif_max = k_dif_max2 = crit_dif_max = c_dif_max = c_dif_max2 = epoch_dif_all = epoch_dif = 0
    dist_max_list, dif_max_list, crit_update_list = [], [], []
    # crit_update_list - список с числом обновлений максимума критерия
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    for crit_list in all_crit_list:
        fun = crit_list[9] # Функция потерь, обеспечившая потенциально лучшее решение
        if sva == 1: crit_max = crit_list[4]
        elif sva == 2: crit_max = crit_list[2]
        elif sva == 0: crit_max = crit_list[6]
        fn = crit_list[7] # Файл, в котором есть потенциально лучшее решение
        dr = crit_list[8] # Папка, в которой находится файл fn
        p = crit_list[10] # Путь к файлу fn
        if sva == 0:
            base = fn[7:]
            fn_acc = p + 'acc' + base
            acc_list = readFile(fn_acc)
            val_acc_list = readFile(p + fn)
            v_list = []
            epoch = -1
            for acc in acc_list:
                epoch += 1
                v_list.append(calc_sum_m(dataSetNum, acc, val_acc_list[epoch]))
        else:
            v_list = readFile(p + fn)
        n_epoch = len(v_list) # Число эпох
        d_max = v_m = v_d_max = v_d_max2 = k_d_max = k_d_max2 = 0
        df_max = k_df_max = k_df_max2 = v_df_max = v_df_max2 = 0
        k = -1
        k_m = k
        nUpdt = -1
        while k < n_epoch - 1:
            for m in range(k_m + 1, n_epoch):
                k += 1
                if k != m: print('k != m'); sys.exit() # Отладка
                v = v_list[m]
                if v > v_m:
                    nUpdt += 1
                    d = m - k_m
                    df = v - v_m
                    if d > d_max:
                        d_max = d
                        v_d_max = v_m; v_d_max2 = v
                        k_d_max = k_m; k_d_max2 = k
                    df_max = df
                    k_df_max = k_m; k_df_max2 = k
                    v_df_max = v_m; v_df_max2 = v
                    v_m = v; k_m = k
        dist_max_list.append([dr, fun, d_max])
        dif_max_list.append([dr, fun, df_max])
        crit_update_list.append([dr, fun, nUpdt, n_epoch])
        if d_max > dist_max:
            dist_max = d_max
            dr_dist_max = dr
            fun_dist_max = fun
            v_dist_max = v_d_max; v_dist_max2 = v_d_max2
            crit_dist_max = crit_max
            k_dist_max = k_d_max; k_dist_max2 = k_d_max2
            epoch_dist_all = crit_list[0]; epoch_dist = crit_list[1]
        if df_max > dif_max:
            dif_max = df_max
            dr_dif_max = dr
            fun_dif_max = fun
            k_dif_max = k_df_max; k_dif_max2 = k_df_max2
            crit_dif_max = crit_max
            c_dif_max = v_df_max; c_dif_max2 = v_df_max2
            epoch_dif_all = crit_list[0]; epoch_dif = crit_list[1]
    if sva == 1: crit_name = 'val_acc'
    elif sva == 2: crit_name = 'acc'
    else: crit_name = 'corr_img'
    def solInf(dr, fun, epoch_all, crit_max, epoch_max, k_max, k_max2, v_max, v_max2):
        print('Замечание. В случае corr_img выводится err_img = all_img - corr_img')
        print('Модель:', dr)
        print('Функция потерь:', fun)
        print('Всего эпох в решении:', epoch_all)
        print('Максимум критерия в решении:', all_img - crit_max if sva == 0 else crit_max)
        print('Эпоха максимума критерия:', epoch_max + 1)
        print('Расстояние между эпохами / критериями:', k_max2 - k_max, '/', v_max2 - v_max)
        print('Эпоха обновления максимума / значение критерия в этой эпохе:',
             k_max + 1, '/', all_img - v_max if sva == 0 else v_max)
        print('Эпоха следующего обновления максимума / значение критерия в этой эпохе:',
             k_max2 + 1, '/', all_img - v_max2 if sva == 0 else v_max2)
    print('\nРешение с наибольшей разницей между номерами эпох обновления максимума критерия', crit_name)
    solInf(dr_dist_max, fun_dist_max, epoch_dist_all, crit_dist_max, epoch_dist,
         k_dist_max, k_dist_max2, v_dist_max, v_dist_max2)
    print('\nРешение с наибольшей разницей между значениями двух последних обновлений максимума критерия', crit_name)
    solInf(dr_dif_max, fun_dif_max, epoch_dif_all, crit_dif_max, epoch_dif,
         k_dif_max, k_dif_max2, c_dif_max, c_dif_max2)
    def plot_v_list(v_list, m, funs, crit_name, y_lb):
        def find_md(dr):
            if dr == 'res_net':
                return 'r_n'
            else:
                dr0 = dr[0]
                if dr0 == 'w':
                    pref = 'c'
                    s = 2
                else:
                    pref = dr0 + 'c'
                    s = 3
                return pref + dr[s:]
        md = fun = ''
        if one_dir == 1:
            fun = find_md(dirs[0])
        else:
            # Находим функцию с наибольшим присутствием в v_list
            n_max = 0
            for f in funs:
                n_f = 0
                for v in v_list:
                    if v[1] == f: n_f += 1
                if n_f > n_max: n_max = n_f; fun = f
        v_list.sort(key = lambda r:r[2])
        n = len(v_list)
        x, y = [], []
        for k in range(n):
            cur_fun = v_list[k][1]
            if one_dir == 1:
                x.append(cur_fun)
                y.append(v_list[k][2] * m)
            else:
                if cur_fun == fun:
                    md = find_md(v_list[k][0])
                    x.append(md)
                    y.append(v_list[k][2] * m)
        print(x); print(y)
        yMin = 0
        yMax = 1.05 * max(y)
        plt.figure(figsize = (6, 2.4))
        ind = np.arange(len(x))
        width = 0.15 # Ширина столбца диаграммы
        plt.ylim(yMin, yMax)
        plt.bar(ind, y, width)
        plt.xlabel('Модель')
        plt.ylabel(y_lb)
        plt.title(crit_name + ', ' + fun + ', ' + imgType)
        plt.xticks(ind, x)
        plt.show()
##        sys.exit()
    if plot_history_values == 1: plot_v_list(dist_max_list, 1, funs, crit_name, 'Расстояние в эпохах')
    if plot_history_values == 2: plot_v_list(dif_max_list, 1 if sva == 0 else 100, funs, crit_name,
                                         'Разница' + ('' if sva == 0 else ', %'))
    if plot_history_values == 3: plot_v_list(crit_update_list, 1, funs, crit_name, 'Обновлений критерия')
#
def allAboutBadFuns(showBadFuns, funs, dirs, pref_list, ks):
    if showBadFuns == 1: dtls = ' с малым числом эпох'
    elif showBadFuns == 2: dtls = ' с неполным числом метрик'
    elif showBadFuns == 3: dtls = ', истории которых имеют разную длину'
    print('Режим проверки txt-файлов: вывод имен функций' + dtls)
    #
    len_d = len(dirs)
    n_bad = 0 # Число решений с малым числом эпизодов, или число функций с неполным числом метрик
    #
    # Формируем список accW лучших функций (по выбранному показатедю) набора по всем ЦО
    # Сохраняем точность на тестовой и обучающей выборках и номер эпохи
    pref = pref_list[2]
    for dr in dirs:
        if len_d == 1: print(dr)
        p = pathToData + dr + '/'
        files = os.listdir(p)
        accF_max = []
        n = 0
        for fn in files:
            fun = find_fun(sva, fn, funs, pref, ks, len_d, showBadFuns)
            if fun == 'skip': continue
            # Число функций потерь. Оно равно 16
            n += 1
            a_list = readFile(p + fn)
            n_bad = when_showBadFuns(fun, showBadFuns, p, fn, n_bad, a_list, dr)
        if n > n_funs: print('Ошибка. Слишком много функций потерь:', n, '. Dir =', dr); sys.exit()
        if n < n_funs: print('Ошибка. Недостаточно функций потерь:', n, '. Dir =', dr); sys.exit()
    if showBadFuns == 1: print('Число функций с малым числом эпох:', n_bad)
    if showBadFuns == 2: print('Число решений с неполным числом метрик:', n_bad)
    if showBadFuns == 3: print('Число решений с txt-файлами различной длины:', n_bad)
#
# Вывод графиков истории обучения (например, d = ' / ')
def plot_hist(is_acc, sva, all_crit_list, pref_list, imgType, crit_name, d, res_number):
    res_number = min(len(all_crit_list) - 1, res_number)
    print('Выводятся графики для решения №', res_number)
    crit_list = all_crit_list[res_number]
    # crit_list:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    fn = crit_list[7]
    dr = crit_list[8]
    fun = crit_list[9]
    p = crit_list[10]
    base = fn[bias:]
    ttl = imgType + d + crit_name + d + fun + d + dr
    if is_acc:
        ks = 0
        lb = 'acc'
        lb2 = 'val_acc'
        loc = 'lower right'
    else:
        ks = 1
        lb = 'loss'
        lb2 = 'val_loss'
        loc = 'center right'
    acc_loss = readFile(p + pref_list[ks] + base)
    val_acc_loss = readFile(p + pref_list[ks + 2] + base)
    plt.figure(figsize = (5, 2))
    plt.plot(acc_loss, color = 'r', label = lb, linestyle = '--')
    plt.plot(val_acc_loss, color = 'g', label = lb2)
    plt.title(ttl)
    plt.legend(loc = loc)
    plt.show()
#
# Вывод диаграммы эффективности функций потерь
# Если showFreqDiag = True, то выводится частотная диаграмма эффективности функций потерь,
# в противном случае выводится диаграмма эффективности функций потерь при обучении одной модели НС
# В обеих диаграммах по оси абсцисс отложены имена функций
# Первая диаграмма создается по всем директориям с историями обучения всех моделей НС
# В первой диаграмме (showFreqDiag = True) по оси ординат отложена частота появления функции
# в списке эффективных функций потерь
# Функции упорядочены по возрастанию частоты
# Вторая диаграмма создается по одной директории с историей обучения одной модели НС
# Во второй диаграмме (showDiag2 = True) по оси ординат отложено значения заданного критерия
# Функции упорядочены по убыванию значения критерия (случай err_img) и возрастанию в противном случае
def plotLossFunsEffDiag(showFreqDiag, imgType, crit_name, all_loss_funs_list, dr, figsize, d):
    ttl = imgType + d + crit_name + d + ((str(dr) + ' НС') if type(dr) == type(0) else dr) # моделей НС
    if one_dir == 1:
        add_ttl = ' (цикл обучения)'
        ttl = ttl.replace('w_', 'c')
    else:
        if mean_eff_all:
            add_ttl = ' (все решения)'
        else:
            crit_bd = low_level[sva]
            if sva == 0:
                sg = ' < '
                crit_bd = all_img - crit_bd
            else:
                sg = ' > '
                crit_bd = round(100 * crit_bd, 2)
            add_ttl = ' (' + crit_name + sg + str(crit_bd) + ')'
    print(ttl + ': диаграмма эффективности функций потерь' + add_ttl)
    if one_dir == 2:
        ks = 1
    else:
        ks = 1 if showFreqDiag else 2
##        k = -1
        for funs_list in all_loss_funs_list:
            if sva == 1:
##                if not mean_eff_all and ks == 2:
##                    k += 1
##                    all_loss_funs_list[k][ks] = all_loss_funs_list[k][ks] * all_loss_funs_list[k][1] / n_of_eff_models
                funs_list[3] = round(funs_list[3] * 100, 2)
                funs_list[4] = round(funs_list[4] * 100, 2)
            else:
                funs_list[3] = int(round(funs_list[3], 0))
                funs_list[4] = int(round(funs_list[4], 0))
            #print(funs_list)
        # ks = 1: сортируем список эффективности по частоте
        # ks = 2: сортируем список эффективности по значению критерия
        all_loss_funs_list.sort(key = lambda r:r[ks], reverse = crit_name == 'err_img')
    #
    n = len(all_loss_funs_list)
    accV0 = []
    accF0 = []
    for k in range(n):
        fun = all_loss_funs_list[k][0]
        vl = all_loss_funs_list[k][ks]
        accV0.append(vl)
        accF0.append(fun)
    if len(accV0) == 0: print('Пустой список решений'); sys.exit()
    accV = []
    accF = []
    accMin = min(accV0)
    accMax = max(accV0)
    if imgType == 'MNIST':
        if sva == 0:
            k_b = 1.25
            k_min = 0.99
            k_max = 1.01
        else:
            if mean_eff_all:
                k_b = 0.925
                k_min = 0.9999
                k_max = 1.0001
            else:
                k_b = 0.85
                k_min = 0.9999
                k_max = 1.0001
    elif imgType == 'EMNIST':
        if sva == 0:
            k_b = 1.25
            k_min = 0.99
            k_max = 1.01
        else:
            if mean_eff_all:
                k_b = 0.975
                k_min = 0.9995 if one_dir == 1 else 0.997
                k_max = 1.0002 if one_dir == 1 else 1.001
            else:
                k_b = 0.85
                k_min = 0.9998
                k_max = 1.00015
    else: # CIFAR10
        if sva == 0:
            k_b = 0.85
            k_min = 0.99
            k_max = 1.01
        else:
            k_b = 0.85
            if mean_eff_all:
                k_min = 0.95 if one_dir == 1 else 0.97
                k_max = 1.0075 if one_dir == 1 else 1.025
            else:
                k_min = 0.995
                k_max = 1.002
    for k in range(n):
        vl = accV0[k]
        if sva == 0:
            if vl < k_b * accMin:
                accV.append(vl)
                accF.append(accF0[k])
        else:
            if vl > k_b * accMax:
                accV.append(vl)
                accF.append(accF0[k])
    accMin = k_min * min(accV)
    accMax = k_max * max(accV)
    if sva > 0 and accMax > 100 and showDiag2: accMax = 100
##        accMin = (0.95 if mean_eff_all else 0.975) * min(accV)
##        accMax = (1.05 if mean_eff_all else (1.005 if sva == 1 else 1.05)) * max(accV)
    if one_dir == 2:
        accV.reverse()
        accF.reverse()
    n = len(accF)
    ind = np.arange(n)
    width = 0.15 # Ширина столбца диаграммы
    plt.figure(figsize = figsize)
    plt.ylim(accMin, accMax)
    plt.bar(ind, accV, width)
    plt.xlabel('Функция потерь')
    plt.ylabel('Частота' if showFreqDiag else crit_name)
    plt.title(ttl + add_ttl)
    plt.xticks(ind, accF)
    plt.show()
#
# Выводит диаграмму потенциально лучших решений
# По оси абссцисс откладывается имя решения, а по оси ординат - значение выбранного критерия обучения
def plotBestResultsDiag(sva, imgType, crit_name, all_img, all_crit_list, nModels, d, n_to_show):
    def solution_name(dr, fun, crit_name, crit_val, epoch):
        if dr == 'res_net':
            s_n = 'r_n'
        else:
            s_name = dr.split('_') # dr - это, например, w_18 или 2w_18
            s0 = s_name[0]
            s_n = ('' if s0 == 'w' else s0[0]) + 'c' + s_name[1]
        s_n += '(' + fun
        s_n_full = s_n + ', ' + crit_name + ' = ' + str(crit_val) + ', ' + str(epoch) + ')'
        s_n += ')'
        return s_n, s_n_full
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    ttl = imgType + d + crit_name + d + (str(nModels) + ' НС') # моделей НС
    print(ttl + ': диаграмма потенциально лучших решений')
    ks = 2 * (3 - sva)
    # ks = 1: сортируем список по acc
    # ks = 2: сортируем список по val_acc
    # ks = 3: сортируем список по err_img
    all_crit_list.sort(key = lambda r:r[ks])
    n = len(all_crit_list)
    n_to_show = min(n_to_show, n)
    print('Всего решений:', n)
    print('Число выводимых решений:', n_to_show)
    accV = []
    accF = []
    m = 0
    for k in range(n):
        m += 1
        k2 = n - m
        epoch = all_crit_list[k2][1]
        dr = all_crit_list[k2][8]
        fun = all_crit_list[k2][9]
        crit_val = all_crit_list[k2][ks]
        crit_val = all_img - crit_val if sva == 0 else crit_val
        accV.append(crit_val)
        s_n, s_n_full = solution_name(dr, fun, crit_name, crit_val, epoch)
        accF.append(s_n)
        print(s_n_full, crit_val)
        if m == n_to_show: break
    accV.reverse()
    accF.reverse()
    accMin = 0.99 * min(accV)
    accMax = 1.01 * max(accV)
    n = len(accF)
    ind = np.arange(n)
    width = 0.15 # Ширина столбца диаграммы
    figsize = (7.2, 2.4)
    plt.figure(figsize = figsize)
    plt.ylim(accMin, accMax)
    plt.bar(ind, accV, width)
    plt.xlabel('Решение')
    plt.ylabel(crit_name)
    plt.title(ttl)
    plt.xticks(ind, accF)
    plt.show()
#
def descr_ep(ep_list, all_img):
    epoch_0 = ep_list[0] # Всего эпох или родительская эпоха
    epoch = ep_list[1]
    acc = round(100. * ep_list[2], 5)
    loss = round(ep_list[3], 6)
    val_acc = round(100. * ep_list[4], 2)
    val_loss = round(ep_list[5], 6)
    corr_img = int(ep_list[6]) # Число верно классифицированных изображений
    err_img = all_img - corr_img
    return epoch_0, epoch, acc, val_acc, loss, val_loss, err_img
#
def say_ep(ttl, epoch_0, epoch, acc, val_acc, loss, val_loss, err_img, print_details):
    if print_details:
        print(ttl, epoch + 1)
        if epoch_0 > 0: print('Родительская эпоха:', epoch_0 + 1)
        print('acc:', acc) # Точность на обучающей выборке
        print('val_acc:', val_acc) # Точность на тестовой выборке
        print('loss:', loss)
        print('val_loss:', val_loss)
        print('err_img:', err_img) # Число неверно классифицированных изображений
    return ttl, epoch, acc, val_acc, loss, val_loss, err_img
#
# Пополняет список crit_list значениями критериев
def fill_in_crit_list(epoch, dataSetNum, bias, fn, dr, fun, p, crit_list):
    base = fn[bias:]
    fn_list = []
    for pref in pref_list: fn_list.append(p + pref + base)
    for fn in fn_list:
        vl_list = readFile(fn)
        #print(str(len(vl_list)), fn)
        crit_list.append(vl_list[epoch])
    corr_img = calc_sum_m(dataSetNum, crit_list[2], crit_list[4])
    crit_list.append(corr_img)
#
def find_corr_img_0(dataSetNum, acc_list, val_acc_list):
    sum_m = 0.
    epoch_m = 0
    epoch = -1
    for acc in acc_list:
        epoch += 1
        sum_cur = calc_sum_m(dataSetNum, acc, val_acc_list[epoch])
        if sum_m <= sum_cur:
            sum_m = sum_cur
            epoch_m = epoch
    return epoch_m, sum_m
#
# Возвращает список с описаниями эпох наибольшим acc или val_acc
# для эпох, содержащихся в списке all_crit_list
# Описание каждой потери содержит следующие сведения:
# - номер родительской эпохи;
# - номер эпохи;
# - значения acc, loss, val_acc, val_loss, corr_img
def showBestValAccs(bias, pref, dataSetNum, all_crit_list):
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    all_acc_list = []
    for epoch_list in all_crit_list:
        epoch_0 = epoch_list[1] # Родительская эпоха
        fn = epoch_list[7]
        dr = epoch_list[8]
        fun = epoch_list[9]
        p = epoch_list[10]
        base = fn[bias:]
        fn_2 = p + pref + base
        vl_list = readFile(fn_2)
        vl_m = max(vl_list)
        acc_list = []
        epoch = -1
        for vl in vl_list:
            epoch += 1
            if abs(vl - vl_m) < .000000000001:
                crit_list = []
                crit_list.append(epoch_0)
                crit_list.append(epoch)
                fill_in_crit_list(epoch, dataSetNum, bias, fn, dr, fun, p, crit_list)
                acc_list.append(crit_list)
        # Сортируем список по corr_img
        if len(acc_list) > 1: acc_list.sort(key = lambda r:(-r[6], r[3]))
        all_acc_list.append(acc_list[0])
    # all_acc_list - это список следующих списков:
    # [epoch_0, epoch, acc, loss, val_acc, val_loss, corr_img]
    return all_acc_list
#
# Возвращает список с описаниями эпох наибольшим числом верно классифицированных изображений
# для эпох, содержащихся в списке all_crit_list
# Описание каждой потери содержит следующие сведения:
# - номер родительской эпохи;
# - номер эпохи;
# - значения acc, loss, val_acc, val_loss, corr_img
def showBestCorr_img(sva, dataSetNum, pref_list, all_crit_list):
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, dr, fun, p]
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    all_sum_list = []
    for epoch_list in all_crit_list:
        epoch_0 = epoch_list[1] # Родительская эпоха
        fn = epoch_list[7]
        dr = epoch_list[8]
        fun = epoch_list[9]
##        print(dr, fun)
        p = epoch_list[10]
        base = fn[bias:]
        fn_acc = p + pref_list[0] + base
        fn_val_acc = p + pref_list[2] + base
        acc_list = readFile(fn_acc)
        val_acc_list = readFile(fn_val_acc)
        epoch_m, sum_m = find_corr_img_0(dataSetNum, acc_list, val_acc_list)
        sum_list = []
        epoch = -1
        for acc in acc_list:
            epoch += 1
            sum_cur = calc_sum_m(dataSetNum, acc, val_acc_list[epoch])
            if abs(sum_m - sum_cur) < .000000000001:
                crit_list = []
                crit_list.append(epoch_0)
                crit_list.append(epoch)
                fill_in_crit_list(epoch, dataSetNum, bias, fn, dr, fun, p, crit_list)
                sum_list.append(crit_list)
        if len(sum_list) > 1: sum_list.sort(key = lambda r:r[3], reverse = False)
        # Добавляем только одно решение - это решение с наименьшим значением loss
        all_sum_list.append(sum_list[0])
    # all_sum_list - это список следующих списков:
    # [epoch_0, epoch, acc, loss, val_acc, val_loss, corr_img]
    return all_sum_list
#
# Возвращает список с описаниями эпох с наименьшими потерями (loss, если pref = 'loss', или val_loss, если pref = 'val_loss')
# для эпох, содержащихся в списке all_crit_list
# Описание каждой потери содержит следующие сведения:
# - номер родительской эпохи;
# - номер эпохи;
# - значения acc, loss, val_acc, val_loss, corr_img
def findBestLosses(sva, dataSetNum, pref, all_crit_list):
    # all_crit_list - это список следующих списков:
    # [epoch_all, acc, loss, val_acc, val_loss, corr_img, epoch, fn, dr, fun, p]
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    all_loss_list = []
    for epoch_list in all_crit_list:
        epoch_0 = epoch_list[1] # Родительская эпоха
        fn = epoch_list[7]
        dr = epoch_list[8]
        fun = epoch_list[9]
        p = epoch_list[10]
        base = fn[bias:]
        fn_2 = p + pref + base
        vl_list = readFile(fn_2)
        v_m = min(vl_list) # Находим лучший показатель
        # Составляем список из описаний эпох, отвечающих лучшему показателю
        loss_list = []
        epoch = -1
        for vl in vl_list:
            epoch += 1
            if abs(v_m - vl) < .000000000001:
                crit_list = []
                crit_list.append(epoch_0)
                crit_list.append(epoch)
                fill_in_crit_list(epoch, dataSetNum, bias, fn, dr, fun, p, crit_list)
                loss_list.append(crit_list)
        if len(loss_list) > 1: loss_list.sort(key = lambda r:r[6], reverse = True)
        # Если решений с наименьшими потерями несколько (len(loss_list) > 1),
        # то добавляем решение с наибольшим числом верно классифицированных изображений
        all_loss_list.append(loss_list[0])
    # all_loss_list - это список следующих списков:
    # [epoch_0, epoch, acc, loss, val_acc, val_loss, corr_img]
    return all_loss_list
#
def calc_sum_m(dataSetNum, acc, val_acc):
    if dataSetNum == 1: # MNIST
        return 10000 * (6 * acc + val_acc)
    elif dataSetNum == 2: # EMNIST
        return 20800 * (6 * acc + val_acc)
    else: # CIFAR-10
        return 10000 * (5 * acc + val_acc)
#
##Решение задается директорией и именем функции (p и fun)
##Алгоритм (sva = 0):
##1. Найти номер лучшей эпохи по показателю corr_img.
##2. Найти в loss-, val_acc- и val_loss-файлах для этой эпохи показатели loss, val_acc и val_loss
##Алгоритм (sva = 1):
##1. Найти, зная p и fun, val_acc-файл.
##2. Прочитать val_acc-файл и найти номер эпохи лучшего val_acc-решения и значение показателя val_acc.
##3. Найти в loss-, val_acc- и val_loss-файлах для этой эпохи показатели loss, val_acc, val_loss и corr_img
##Алгоритм (sva = 2):
##1. Найти, зная p и fun, acc-файл.
##2. Сформировать имена loss-, val_acc- и val_loss-файлов.
##3. Прочитать acc-файл и найти номер эпохи лучшего acc-решения и значение показателя acc.
##Для каждого лучшего решения:
##1. Найти в соответствующем эпизоде el - номер эпохи с наименьшим значением показателя loss.
##2. Найти описание этой эпохи.
##3. Найти в соответствующем эпизоде evl - номер эпохи с наименьшим значением показателя val_loss.
##4. Найти описание этой эпохи.
# Находит описание эпохи (acc, loss, val_acc, val_loss, corr_img) по ее номеру epoch
def find_criterias(sva, dataSetNum, all_epoch_list, pref_list):
    # all_epoch_list - это список следующих списков [epoch_all, epoch_0, fn, dr, fun, p]
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    all_crit_list = []
    for epoch_list in all_epoch_list:
        #print(epoch_list)
        epoch_0 = epoch_list[1]
        crit_list = []
        crit_list.append(epoch_list[0])
        crit_list.append(epoch_0)
        fn = epoch_list[2]
        dr = epoch_list[3]
        fun = epoch_list[4]
        p = epoch_list[5]
        fill_in_crit_list(epoch_0, dataSetNum, bias, fn, dr, fun, p, crit_list)
        for k in range(2, 6): crit_list.append(epoch_list[k])
        all_crit_list.append(crit_list)
##        print(crit_list)
    return all_crit_list
#
def find_corr_img(dataSetNum, p, fn, val_acc_list):
    fn_acc = fn[4:]
    acc_list = readFile(p + fn_acc)
    sum_m = 0.
    epoch_m = 0
    epoch = -1
    for acc in acc_list:
        epoch += 1
        val_acc = val_acc_list[epoch]
        sum_cur = calc_sum_m(dataSetNum, acc, val_acc)
        if sum_m < sum_cur:
            sum_m = sum_cur
            epoch_m = epoch
    return epoch_m, sum_m
#
def readFile(fn):
    # В файле fn точность на тестовой выборке после каждой эпохи
    with open(fn, 'r') as f: accV = f.readlines()
    if accV[len(accV) - 1] == '': del accV[-1]
    n = -1
    for a in accV:
        n += 1
        accV[n] = float(a)
    return accV
#
# Находит имя функции потерь в имени файла
def find_fun(sva, fn, funs, pref, ks, len_d = 0, showBadFuns = 0):
    flag = fn[0:len(pref)] == pref
    if not flag: return 'skip'
    k = fn.find('_Adam')
    if k == -1: k = fn.find('_conv')
    if k == -1: print('ERROR:', fn); sys.exit()
    ksv = len(pref) + ks
    fun = fn[ksv:k]
    if fun == 'myLoss': return 'skip'
    elif fun == 'mse17_kld': fun = 'mk'
    elif fun == 'cce_kld': fun = 'ck'
    if len_d == 1 and showBadFuns == 0: print('Loss fun:', fun)
    try: k = funs.index(fun)
    except: k = -1; print('ERROR. Не найдена функция потерь:', fun); sys.exit()
    return 'skip' if oneFun and fun != oneFunName else fun
#
def file_names(p, fn):
    # sva = 1, поэтому:
    fn_loss = 'loss' + fn[7:]
    # acc, loss, val_acc, val_loss
    return [p + fn[4:], p + fn_loss, p + fn, p + 'val_' + fn_loss]
#
def when_showBadFuns(fun, showBadFuns, p, fn, n_bad, a_list, dr):
    fns = file_names(p, fn)
    if showBadFuns == 1:
        if len(a_list) < 10 or not os.path.exists(fns[0]):
            n_bad += 1 # Число решений с малым числом эпизодов
            print(str(n_bad) + '.', fun, '/', dr)
    elif showBadFuns == 2:
        is_fn_acc = os.path.exists(fns[1])
        is_fn_val_loss = os.path.exists(fns[2])
        is_fn_loss = os.path.exists(fns[3])
        miss = ''
        if not is_fn_acc: miss += 'acc; '
        if not is_fn_loss: miss += 'loss; '
        if not is_fn_val_loss: miss += 'val_loss; '
        if len(miss) > 0:
            n_bad += 1 # Число решений с неполным числом метрик
            print(str(n_bad) + '.', fun, '/', dr, '/ Missing:', miss)
    elif showBadFuns == 3: # Файлы с разной длинной
        is_fn_acc = os.path.exists(fns[0])
        is_fn_loss = os.path.exists(fns[1])
        is_fn_val_acc = os.path.exists(fns[2])
        is_fn_val_loss = os.path.exists(fns[3])
        is_fn = is_fn_acc and is_fn_loss and is_fn_val_acc and is_fn_val_loss
        if not is_fn:
            print(fns[0])
            print(fns[1])
            print(fns[2])
            print(fns[3])
            print(fun, '/', dr, ': нужно выполнить программу с showBadFuns = 2'); sys.exit()
        la = len(readFile(fns[0]))
        ll = len(readFile(fns[1]))
        lva = len(readFile(fns[2]))
        lvl = len(readFile(fns[3]))
        eq_len = la == lva and la == ll and la == lvl
        if not eq_len:
            n_bad += 1 # Число решений с txt-файлами разной длины
            print(str(n_bad) + '.', fun, '/', dr, 'acc: ' + str(la), '; loss: ' + str(ll) +
                 '; val_acc: ' + str(lva) + '; val_loss:' + str(lvl))
    return n_bad
#
def find_epoch_0(dataSetNum, fun, dr, vl_list):
    # Может быть при sva = 0 (err_img, mean_eff_all = True) и sva = 1 (val_acc - точность на тестовой выборке)
    vl_m = max(vl_list)
    epoch_m = vl_list.index(vl_m)
    return epoch_m, vl_m
#
def find_epoch(sva, dataSetNum, v_m, fun, dr, vl_list, fn_2, fn_loss, low_level):
    # epoch_list - это список [epoch, vl_m, corr_img, loss]
    crit_low = low_level[sva]
    #print(crit_low)
    epoch_list = []
    vl_list_2 = readFile(fn_2) # val_acc, если fn - это acc, или acc, если fn - это val_acc
    loss_list = readFile(fn_loss) # Потери на обучающей выборке
    epoch = -1
    for vl in vl_list:
        epoch += 1
        if vl > crit_low:
            vl_2 = vl_list_2[epoch]
            if sva == 1:
                corr_img = calc_sum_m(dataSetNum, vl_2, vl)
            else:
                corr_img = calc_sum_m(dataSetNum, vl, vl_2)
            epoch_list.append([epoch, vl, corr_img, loss_list[epoch]])
    # Если несколько одинаковых решений по заданному критерию, то берем решение,
    # обеспечивающее максимальное число верно классифицируемых изображений
    # Если таковых несколько, то берем из них решение с наименьшими потерями на обучающей выборке (loss)
    if len(epoch_list) > 0:
        if len(epoch_list) > 1: epoch_list.sort(key = lambda r:(-r[1], r[3]))
        epoch_m = epoch_list[0][0]
        vl_m = epoch_list[0][1]
    else:
        epoch_m = vl_m = -1
    return epoch_m, vl_m
#
# Возвращает в зависимости от sva описание лучшего решения в серии набора
# В описание входят:
# - функция потерь и имя модели, отвечающие лучшему решению;
# - общее число эпох;
# - номер эпохи получения лучшего решения;
# - значения критериев и функции потерь на тестовой и обучающей выборке в этой эпохе;
# - номер эпохи для эпизода с минимальным значением функции потерь на тестовой выборке;
# - значения критериев и функции потерь на тестовой и обучающей выборке в этой эпохе;
# - номер эпохи для эпизода с минимальным значением функции потерь на обучающей выборке;
# - значения функции потерь и критериев в этой эпохе.
# Критерии:
# - число неверно классифицированных изображений на обеих выборках;
# - точность на тестовых данных;
# - точность на обучающих данных.
def find_max_in_dirs(sva, dataSetNum, dirs, pathToData, pref_list, ks):
    if sva == 2:
        pref = pref_list[0] # acc, loss, val_acc, val_loss
        pref_2 = pref_list[2]
    else:
        pref = pref_list[2]
        pref_2 = pref_list[0]
    pref_loss = pref_list[1]
    # Первый проход. Находим лучший показатель
    v_m = 0.
    epoch_m = 0
    fun_m = ''
    for dr in dirs:
        #print(dr)
        p = pathToData + dr + '/'
        files = os.listdir(p)
        for fn in files:
            fun = find_fun(sva, fn, funs, pref, ks)
            if fun == 'skip': continue
            vl_list = readFile(p + fn)
            if sva == 0: # Поиск номеров эпох лучших решений по критерию corr_img
                epoch, v_epoch = find_corr_img(dataSetNum, p, fn, vl_list)
            else:
                epoch, v_epoch = find_epoch_0(dataSetNum, fun, dr, vl_list)
            if v_m < v_epoch:
                v_m = v_epoch
                epoch_m = epoch
                fun_m = fun
##    print(v_m, epoch_m, fun_m)
##    sys.exit()
    # Второй проход. Составляем список из лучших решений
    bias = 7 if sva < 2 else 3 # sva = 1 (2) - Точность на тестовых (обучающих) данных
    all_epoch_list = []
    for dr in dirs:
        p = pathToData + dr + '/'
        files = os.listdir(p)
        for fn in files:
            fun = find_fun(sva, fn, funs, pref, ks)
##            print(dr, fun)
            if fun == 'skip': continue
            vl_list = readFile(p + fn)
            if sva == 0: # Поиск номеров эпох лучших решений по критерию corr_img
                fn_acc = fn[4:]
                acc_list = readFile(p + fn_acc)
                epoch_0, v_epoch = find_corr_img_0(dataSetNum, acc_list, vl_list)
                if v_epoch < low_level[0]: epoch_0 = -1
            else:
                fn_b = fn[bias:]
                #print(fn_b)
                epoch_0, v_epoch = find_epoch(sva, dataSetNum, v_m, fun, dr, vl_list,
                                             p + pref_2 + fn_b, p + pref_loss + fn_b, low_level)
            if epoch_0 > -1:
                epoch_list = [len(vl_list), epoch_0, fn, dr, fun, p]
                all_epoch_list.append(epoch_list)
##                print(epoch_list)
    if len(all_epoch_list) == 0:
        print('Ошибка второго прохода. Имена префиксов файлов истории обучения:', pref, pref_2)
        print('Нужно поправить список low_level')
        sys.exit()
    all_crit_list = find_criterias(sva, dataSetNum, all_epoch_list, pref_list)
    if len(all_crit_list) > 1:
        if sva == 0:
            # Сортируем по убыванию числа верно классифицированных изображений
            # и возрастанию точности на обучающей и тестовой выборках
            all_crit_list.sort(key = lambda r: (-r[6], r[4]))
        else:
            # Сортируем убыванию точности на тестовой (sva = 1) или обучающей выборках
            # и по убыванию числа верно классифицированных изображений
            if sva == 1:
                all_crit_list.sort(key = lambda r: (-r[4], -r[6]))
            else:
                all_crit_list.sort(key = lambda r: (-r[2], -r[6]))
    # all_crit_list - это список следующих списков:
    # [epoch_all, epoch_0, acc, loss, val_acc, val_loss, corr_img, fn, model_name (dr), fun, pathToHistory]
    return all_crit_list
# Вывод списка эффективных моделей
def show_eff_models(all_crit_list):
    eff_models_list = []
    for crit_list in all_crit_list:
        m_n = crit_list[8]
        if not m_n in eff_models_list: eff_models_list.append(m_n)
    print('\nСписок эффективных моделей:')
    m_n_str = ""
    for m_n in eff_models_list:
        m_n_str = m_n_str + ", '" + m_n + "'"
    print('all_dirs = [' + m_n_str[2:] + ']')
    n_of_eff_models = len(eff_models_list)
    print('Всего эффективных моделей:', n_of_eff_models)
    return n_of_eff_models
# Корректируем значения val_acc для ФП "косинусная близость"
def check_cp(dataSetNum, all_crit_list):
    for crit_list in all_crit_list:
        if crit_list[9] == 'cp':
            if dataSetNum == 1:
                cp_val_acc = 0.9925
                if crit_list[4] < cp_val_acc: crit_list[4] = cp_val_acc
            elif dataSetNum == 2:
                cp_val_acc = 0.89
                if crit_list[4] < cp_val_acc: crit_list[4] = cp_val_acc
            elif dataSetNum == 3:
                cp_val_acc = 0.57
                if crit_list[4] < cp_val_acc: crit_list[4] = cp_val_acc
#
# Набор данных (вид изображений)
if dataSetNum == 1: # MNIST
    imgType = 'MNIST'
    all_img = 70000
elif dataSetNum == 2: # EMNIST
    imgType = 'EMNIST'
    all_img = 145600
elif dataSetNum == 3:# CIFAR-10
    imgType = 'CIFAR10'
    all_img = 60000
print('Набор данных:', imgType)
print('Всего изображений:', all_img)
#
# Критерии:
# acc - точность классификации на обучающей выборке
# loss - потери на обучающей выборке
# val_acc - точность классификации на тестовой выборке
# val_loss - потери на тестовой выборке
# corr_img - суммарное число верно классифицированных изображений на обучающей и тестовых выборках
#
if showBadFuns == 0:
    if sva == 0: print('Критерий: число ошибочно классифицированных изображений на обеих выборках; sva =', sva)
    elif sva == 1: print('Критерий: точность на тестовой выборке; sva =', sva)
    elif sva == 2: print('Критерий: точность на обучающей выборке; sva =', sva)
    else: print('Плохое значение номера критерия; sva =', sva); sys.exit()
#
# Цикл обучения (ЦО) - это обучение модели с применением всех 16 функций потерь
# Отбираем в каждом ЦО решения с критерием, не меньшим заданной нижней границы low_level
# Если значение критерия больше low_level, то функция, обеспечившая это значение, добавляется в число эффективных
#
pathToData = '' # Путь к каталогу с результатами
p0 = 'G:/AM/НС/_результаты/'
pref_list = ['acc', 'loss', 'val_acc', 'val_loss'] # Префикс в имени файла
#
if dataSetNum == 1:
    # ks - добавка к длине префикса для поиска стартовой позиция поиска имени функции в имени
    # acc-, loss-, val_acc- или val_loss-файла
    ks = 7
    # Нижняя граница оценки результатов обучения по err_img, val_acc и acc
    if one_dir == 0:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 60, low_level_1, 0.999]
    elif one_dir == 1:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 60, 0.9957, 0.999]
    elif one_dir == 2:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 350, 0.993, 0.999] # [all_img - 350, 0.9957, 0.999]
    pathToData = p0 + 'mnist_results/'
##    pathToData = 'G:/100byte/python/imgClasses/mnistHistories/'
    if lossFunsEffDiag:
        all_dirs = ['w_1', 'w_4', 'w_5', 'w_6', 'w_7', 'w_8', 'w_9', 'w_10', 'w_11',
                    'w_12', '2w_12', '2w_12_2', '2w_12bn', '3w_12', 'w_13', 'w_14',
                    'w_15', 'w_16', 'w_17', 'w_18', 'w_34bn', 'res_net']
    else:
        all_dirs = ['2w_12bn', '2w_12_2', '3w_12', 'w_1', 'w_9', 'w_11', 'w_12', 'w_13', 'w_15', 'w_16'] # 'w_34bn'
        if has_best_nn: all_dirs.append(best_nn)
    #
elif dataSetNum == 2:
    ks = 8
    # Нижняя граница оценки результатов обучения по err_img, val_acc и acc
    if one_dir == 0:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 3500, low_level_1, 0.9825]
    elif one_dir == 1:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 3900, 0.9484, 0.9825]
    elif one_dir == 2:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 5000, 0.9487, 0.9825]
    pathToData = p0 + 'emnist_results/'
##    pathToData = 'G:/100byte/python/imgClasses/emnistHistories/'
##    all_dirs = ['w_5', 'w_7', 'w_19', 'w_20', '2w_20', '3w_20', 'w_21', 'w_9', 'w_11',
##                'w_12', '2w_12', '3w_12', 'w_4', 'w_13', 'w_14', 'w_10', 'w_24', 'w_25',
##                'w_26', 'w_27', 'w_28', '2w_24', '2w_27', '2w_28', '2w_28_2', '2w_28bn', 'w_15',
##                'w_17', 'w_1', 'w_3', 'w_34bn', 'res_net']
    all_dirs = ['2w_28bn', '2w_28', '2w_27', '2w_24', 'w_28', 'w_24', '2w_12', '3w_12', 'w_3', '2w_20'] # 'w_34bn'
    if has_best_nn: all_dirs.append(best_nn)
elif dataSetNum == 3:
    ks = 9
    # Нижняя граница оценки результатов обучения по err_img, val_acc и acc
    if one_dir == 0:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 3300, low_level_1, 0.97]
    elif one_dir == 1:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 7500, 0.8, 0.95]
    elif one_dir == 2:
        low_level = [0, 0.0, 0.0] if mean_eff_all else [all_img - 6000, 0.9, 0.97]
    pathToData = p0 + 'cifar_results/'
##    pathToData = 'G:/100byte/python/imgClasses/cifar10Histories/'
##    all_dirs = ['w_5', 'w_7', 'w_4', 'w_13', 'w_14', '2w_14', '3w_14', 'w_15', 'w_16',
##                'w_17', 'w_18', 'w_33', 'w_34bn', 'w_1', 'res_net']
    all_dirs = ['w_34bn', 'w_1', '3w_14', 'w_33', 'w_18', 'w_16', 'w_15', '2w_14', 'w_17', 'w_14'] # 'res_net'
    if has_best_nn: all_dirs.append(best_nn)
else:
    print('Плохой номер набора данных')
    sys.exit()
#
if showBadFuns != 0: sva = 1 # Точность на тестовой выборке
#
funs = ['mse', 'mae', 'mape', 'msle', 'sh', 'h', 'ch', 'lc', 'cce', 'scce', 'bce', 'kld', 'pss', 'cp', 'ck', 'mk']
print('Число сканируемых директорий:', len(all_dirs))
#
if showBadFuns == 0:
    if one_dir < 2:
        if one_dir == 1:
##            if not w_dir in all_dirs:
##                print('Плохое имя папки:', w_dir)
##                sys.exit()
            print('Имя НС', w_dir)
            dirs = [w_dir]
        else:
            dirs = all_dirs
        # Находим значения критериев, отвечающих лучшему sva-решению, в серии набора
        # all_crit_list - это список следующих списков:
        # [epoch_all, epoch, acc, loss, val_acc, val_loss, corr_img, fn, model_name (dr), fun, pathToHistory]
        all_crit_list = find_max_in_dirs(sva, dataSetNum, dirs, pathToData, pref_list, ks)
        if sva == 1:
            # Вывод списка приоритетных рещений
            print('Список приоритетных рещений:')
            # Корректировка аномальных значений val_acc
            #k = -1
            for crit_list in all_crit_list:
                lfd_list = [] # Список с атрибутами ФП-решения: [Имя НС, Имя ФП, val_acc_ФП]
                dr = crit_list[8]
                dr = dr.replace('w', 'c')
                dr = dr.replace('_', '')
                lfd_list.append(dr)
                lfd_list.append(crit_list[9])
                lfd_list.append(round(100 * crit_list[4], 2))
                print(lfd_list)
                #k += 1
                #if crit_list[4] < 0.552: all_crit_list[k][4] = 0.552
            #print('Выполнена корректировка аномальных значений val_acc')
        #
        #if sva == 1: check_cp(dataSetNum, all_crit_list) # Корректируем значения val_acc для ФП "косинусная близость"
        if not show_all_best:
            # Максимальное расстояние между эпохами обновления максимума критерия
            # и максимальная разница между двумя последними обновлениями критерия
            find_history_values(sva, dataSetNum, all_img, funs, all_crit_list, plot_history_values)
        # Находим описания эпох наименьших потерь, отвечающих решениям, занесенным в all_crit_list
        # all_loss_list - это список следующих списков:
        # [epoch_0, epoch, acc, loss, val_acc, val_loss, corr_img]
        all_loss_list = findBestLosses(sva, dataSetNum, pref_list[1], all_crit_list)
        all_val_loss_list = findBestLosses(sva, dataSetNum, pref_list[3], all_crit_list)
        if findLossesDif:
            find_losses_dif(all_crit_list, all_loss_list)
        #
        if not mean_eff_all: print('\nВсего потенциально лучших решений:', len(all_crit_list))
        # Находим описания эпох с набольшим числом классифицированных изображений
        if sva > 0:
            all_sum_list = showBestCorr_img(sva, dataSetNum, pref_list, all_crit_list)
        if sva == 1 or sva == 0:
            all_acc_list = showBestValAccs(7, 'acc', dataSetNum, all_crit_list)
        if sva == 2 or sva == 0:
            all_val_acc_list = showBestValAccs(3 if sva == 2 else 7, 'val_acc', dataSetNum, all_crit_list)
        #
        # Показываем лучшие решения
        if print_epoch_descr:
            showBestSolutions(sva, show_all_best, all_crit_list, all_img)
        if bestResultsDiag:
            plotBestResultsDiag(sva, imgType, crit_name, all_img, all_crit_list, len(all_dirs), ' / ', n_to_show)
            sys.exit()
        #
        if not mean_eff_all: # Выводим список эффективных моделей
            n_of_eff_models = show_eff_models(all_crit_list)
        #
        # Выделяем эффективные функции потерь
        bestLossFuns(sva, showFreqDiag, showDiag2, plot_acc_loss, print_eff_funs_list, imgType, res_number, all_crit_list)
    elif one_dir == 2:
        scanDirsOneByOne(all_dirs)
    else:
        print('Плохой режим анализа историй обучения')
elif showBadFuns < 0 or showBadFuns > 5:
    print('Плохой режим поиска ошибок. showBadFuns =', showBadFuns)
else:
    if showBadFuns < 4:
        allAboutBadFuns(showBadFuns, funs, all_dirs, pref_list, ks)
    elif showBadFuns == 4:
        # Среднее число эпох в историях обучения
        avgEpchsNumber(sva, funs, all_dirs, pref_list, ks, pathToData)
    elif showBadFuns == 5:
        # Сравнение потерь mse и kld. Для обоснования функции mk
        # Вычисляем суммы потерь и большую сумму делим на меньшую
        compare_for_mk(imgType, all_dirs, pathToData)

Приложение 6. Обработка историй обучения

Программа анализирует истории обучения (ИО) НС, сохраненные для набора данных, заданного параметром dataSetNum:
MNIST: dataSetNum = 1;
EMNIST: dataSetNum = 2;
CIFAR10: dataSetNum = 3.
Для каждой функции потерь (ФП) должно быть сохранено 4 txt-файла, например, для модели c7 должны быть сохранены следующие файлы (расширение .txt опущено):
acc_MNIST_cce_Adam_c7, loss_MNIST_cce_Adam_c7, val_acc_MNIST_cce_Adam_c7, val_loss_MNIST_cce_Adam_c7.
Эти файлы содержат ИО модели c7 с ФП cce на наборе данных MNIST.

П6.1. Проверка историй обучения

Выполняются следующие проверки историй обучения:
showBadFuns = 1 – вывод имен функций с малым числом эпох;
showBadFuns = 2 – вывод имен функций с неполным числом метрик;
showBadFuns = 3 – вывод имен функций, истории которых имеют разную длину.
Всего анализируются истории 16-и функций потерь (ФП).
showBadFuns = 1: история считается неполной, если в ней менее 10 записей (каждая запись отвечает одной эпохе).
showBadFuns = 2: для каждой ФП в истории должно быть 4 файла, содержащих значения acc, loss, val_acc и val_loss.
showBadFuns = 3: во всех 4-х файлах ИО, полученной в результате обучения модели НС, должно быть одинаковое число записей.
При нарушении этих условий программой проверки выдается соответствующая информация.

П6.2. Два статистических показателя

showBadFuns = 4: находим среднее число эпох в историях обучения. Пример результата:
Число сканируемых директорий: 20
Набор данных: MNIST
Всего изображений: 70000
Всего эпох: 23653
Всего файлов: 320
Среднее число эпох: 74.0
showBadFuns = 5: выполняем сравнение потерь mse и kld и cce и kld. Полезно для обоснования функций mk и ck.
Параметр one_dir = 0. Если one_dir = 1, то результат будет получен для указанной модели (параметр w_dir), например, w_dir = 'w_34'.
Пример результата (one_dir = 0):
Число сканируемых директорий: 20
Набор данных: MNIST
Всего изображений: 70000
w_5
loss_MNIST_cce_Adam_c5.txt
loss_MNIST_kld_Adam_c5.txt
loss_MNIST_mse_Adam_c5.txt
...
w_34
loss_MNIST_cce_Adam_c34.txt
loss_MNIST_kld_Adam_c34.txt
loss_MNIST_mse_Adam_c34.txt
Коэффициент превышения потерь kld над потерями mse: 16.49296346369197
Коэффициент превышения потерь cce над потерями kld: 1.002296572706515
То есть использование ФП
17mse + kld
и
cce + kld
правомерно.

П6.3. Поиск лучших решений

Параметры:
Номер набора данных
dataSetNum: 1 - MNIST; 2 - EMNIST; 3 - CIFAR10
showBadFuns = 0
sva = 0 – поиск лучших решений по критерию corr_img (err_img = all_img - corr_img)
sva = 1 – поиск лучших решений по критерию val_acc
sva = 2 – поиск лучших решений по критерию acc
one_dir = 0 – поиск лучших решений по всем моделям; при one_dir = 0 выполняется отсев слабых моделей.
one_dir = 1 – поиск лучших решений для заданной модели;
one_dir = 2 – перебор моделей и поиск лучших решений в текущей модели; при one_dir = 2 все модели участвуют в подготовке данных.
sva = 0 – поиск лучших решений по критерию corr_img (err_img = all_img - corr_img)
sva = 1 – поиск лучших решений по критерию val_acc
sva = 2 – поиск лучших решений по критерию acc.
show_all_best = True - для каждой модели и ФП выводится описание эпохи, отвечающей лучшему решению по заданному критерию
Так, в случае 20 моделей будет выведено описание 320 эпох
Если show_all_best = False, то показывается первое лучшее решение
Если show_all_best = True, то поиск характеристик историй обучения и эффективных функций потерь не выполняется
print_epoch_descr – флаг вывода описаний эпох. Использовать при one_dir = 1, иначе будет слишком много печати): для каждой модели и ФП выводится описание эпохи, отвечающей лучшему решению по заданному критерию. Так, в случае 20 моделей будет выведено описание 320 эпох.
lossFunsEffDiag – флаг вывода диаграммы средневзвешенной эффективности ФП.
print_details – флаг вывода дополнительной информации о лучших решениях (True лучше не использовать).
print_funs_list – флаг вывода списка эффективных функций потерь.
mean_eff_all – флаг поиска средневзвешенной оценки эффективности ФП по СЛР (False) или по все серии обучения (True). СЛР – список лучших решений.
При sva = 1 для отбора эффективных (mean_eff_all = False) и равнозначных (one_dir = 1) ФП используется погрешность эксперимента Delta.
Первоначально при sva = 1 используется режим mean_eff_all = True: по результатам обработки историй обучения определяется нижняя граница val_acc, используемая при формировании СЛР (заносится в low_level[1] при one_dir = 0).
Пример результата при параметрах
dataSetNum = 1, one_dir = 0, sva = 1, show_all_best = False, lossFunsEffDiag = False, print_details = False, print_funs_list = False, mean_eff_all = True, oneFun = False, findLossesDif = False:
Критерий: точность на тестовой выборке; sva = 1
Число сканируемых директорий: 20
Набор данных: MNIST
Всего изображений: 70000
Всего потенциально лучших решений: 320
Первое лучшее решение:
1.
['val_acc_MNIST_ck_Adam_c34.txt', 'w_34', 'ck']
Всего эпох: 87
Описание эпох:
('Эпоха лучшего решения по val_acc:', 56, 99.76, 99.62, 0.016765, 0.041646, 182)
('Эпоха наибольшей точности acc:', 86, 99.87667, 99.59, 0.009406, 0.060694, 116)
('Эпоха наименьших loss-потерь:', 86, 99.87667, 99.59, 0.009406, 0.060694, 116)
('Эпоха наименьших val_loss-потерь:', 6, 99.09334, 99.48, 0.057546, 0.030292, 596)
('Эпоха с наименьшим err_img:', 86, 99.87667, 99.59, 0.009406, 0.060694, 116)
Если задать
lossFunsEffDiag = True,
то будут выведены списки, содержащие
- имя ФП;
- число обученных моделей НС;
- средневзвешенную эффективность ФП (вычисляется как среднее по всем решениям);
- минимальная эффективность;
- максимальная эффективность.
Вдобавок будет показана диаграмма средневзвешенной эффективности всех ФП.
Пример:
MNIST / val_acc / 20 НС: диаграмма по серии обучения
['mae', 20, 94.99, 11.35, 99.51]
['cp', 20, 95.0, 10.32, 99.56]
['h', 20, 99.31, 97.84, 99.56]
['mape', 20, 99.32, 98.15, 99.53]
['ch', 20, 99.33, 98.62, 99.51]
['lc', 20, 99.42, 99.18, 99.55]
['sh', 20, 99.42, 98.97, 99.54]
['bce', 20, 99.45, 99.32, 99.61]
['mse', 20, 99.45, 99.35, 99.6]
['cce', 20, 99.45, 99.28, 99.58]
['msle', 20, 99.45, 99.3, 99.57]
['ck', 20, 99.46, 99.3, 99.62]
['mk', 20, 99.46, 99.32, 99.62]
['pss', 20, 99.46, 99.29, 99.62]
['scce', 20, 99.46, 99.31, 99.59]
['kld', 20, 99.47, 99.34, 99.6]
Данные упорядочены по третьему элементу списка – средневзвешенной эффективности ФП.
show_all_best = True (лучше не использовать) – фрагмент результата:
317.
['val_acc_MNIST_mape_Adam_c34.txt', 'w_34', 'mape']
Всего эпох: 92
Описание эпох:
('Эпоха лучшего решения по val_acc:', 61, 96.855, 98.15, 3144149.152733, 1850000.256, 2072)
('Эпоха наибольшей точности acc:', 61, 96.855, 98.15, 3144149.152733, 1850000.256, 2072)
('Эпоха наименьших loss-потерь:', 61, 96.855, 98.15, 3144149.152733, 1850000.256, 2072)
('Эпоха наименьших val_loss-потерь:', 61, 96.855, 98.15, 3144149.152733, 1850000.256, 2072)
('Эпоха с наименьшим err_img:', 61, 96.855, 98.15, 3144149.152733, 1850000.256, 2072)
Также для этого решения будет выведен следующий список:
['w_34', 'mape', 96.855, 2072.0]
Списки подобного вида предваряются следующим заголовком:
Значение критериев acc и err_img для лучших решений по val_acc
Если
print_funs_list = True – флаг вывода списка эффективных функций потерь.
то будет дополнительно выведена следующая информация (случай sva = 1):
Список эффективных функций потерь по критерию val_acc:
Элемент списка содержит: имя ФП, среднее, минимальное и максимальное значение критерия
1 ['kld', 20, 99.47, 0.9934, 0.996]
2 ['ck', 20, 99.46, 0.993, 0.9962]
3 ['mk', 20, 99.46, 0.9932, 0.9962]
4 ['pss', 20, 99.46, 0.9929, 0.9962]
5 ['scce', 20, 99.46, 0.9931, 0.9959]
6 ['bce', 20, 99.45, 0.9932, 0.9961]
7 ['mse', 20, 99.45, 0.9935, 0.996]
8 ['cce', 20, 99.45, 0.9928, 0.9958]
9 ['msle', 20, 99.45, 0.993, 0.9957]
10 ['lc', 20, 99.42, 0.9918, 0.9955]
11 ['sh', 20, 99.42, 0.9897, 0.9954]
12 ['ch', 20, 99.33, 0.9862, 0.9951]
13 ['mape', 20, 99.32, 0.9815, 0.9953]
14 ['h', 20, 99.31, 0.9784, 0.9956]
15 ['cp', 20, 95.0, 0.1032, 0.9956]
16 ['mae', 20, 94.99, 0.1135, 0.9951]
Данные упорядочены по третьему элементу списка – средневзвешенной эффективности ФП.

П6.4. Анализ одной модели

Выполняется, если one_dir = 1. Модель задается параметром w_dir, например, w_dir = 'res_net'.
Пример результата при параметрах
dataSetNum = 3, one_dir = 1, sva = 1, show_all_best = True, lossFunsEffDiag = False, print_details = False, print_funs_list = False, w_dir = 'res_net', mean_eff_all = True, oneFun = False, findLossesDif = False:
Критерий: точность на тестовой выборке; sva = 1
Число сканируемых директорий: 15
Набор данных: CIFAR10
Всего изображений: 60000
Всего потенциально лучших решений: 16
Лучшие решения:
1.
['val_acc_CIFAR10_pss_Adam_cRNv1_3_16_BR_SH_DA.txt', 'res_net', 'pss']
Всего эпох: 200
Описание эпох:
('Эпоха лучшего решения по val_acc:', 155, 98.264, 91.62, 0.117225, 0.143267, 1706)
('Эпоха наибольшей точности acc:', 184, 98.436, 91.48, 0.116746, 0.143488, 1634)
('Эпоха наименьших loss-потерь:', 189, 98.412, 91.45, 0.116686, 0.143451, 1649)
('Эпоха наименьших val_loss-потерь:', 137, 97.954, 91.39, 0.118329, 0.142858, 1885)
('Эпоха с наименьшим err_img:', 184, 98.436, 91.48, 0.116746, 0.143488, 1634)
...
16.
['val_acc_CIFAR10_cp_Adam_cRNv1_3_16_BR_SH_DA.txt', 'res_net', 'cp']
Всего эпох: 31
Описание эпох:
('Эпоха лучшего решения по val_acc:', 4, 10.0, 5.0, 0.1, 0.1, 54500)
('Эпоха наибольшей точности acc:', 0, 10.018, 5.0, 0.101004, 0.1, 54491)
('Эпоха наименьших loss-потерь:', 4, 10.0, 5.0, 0.1, 0.1, 54500)
('Эпоха наименьших val_loss-потерь:', 0, 10.018, 5.0, 0.101004, 0.1, 54491)
('Эпоха с наименьшим err_img:', 0, 10.018, 5.0, 0.101004, 0.1, 54491)
Значение критериев acc и err_img для лучших решений по val_acc
['res_net', 'cce', 99.166, 1347.0000000000073]
['res_net', 'scce', 99.08, 1394.0]
['res_net', 'kld', 99.008, 1386.0]
['res_net', 'ck', 98.91799999999999, 1518.0]
['res_net', 'mk', 98.842, 1520.0]
['res_net', 'pss', 98.264, 1706.0]
['res_net', 'bce', 97.868, 1961.0000000000073]
['res_net', 'mse', 95.212, 3385.9999999999927]
['res_net', 'sh', 95.206, 3383.0]
['res_net', 'msle', 94.216, 3981.0]
['res_net', 'ch', 93.56400000000001, 4337.0]
['res_net', 'lc', 93.42200000000001, 4374.0]
['res_net', 'mae', 88.188, 7397.0]
['res_net', 'mape', 72.734, 16713.000000000007]
['res_net', 'h', 71.326, 17431.0]
['res_net', 'cp', 10.0, 54500.0]
Нижняя граница критерия: 0.0
Список эффективных функций потерь по критерию val_acc:
Элемент списка содержит: имя ФП, среднее, минимальное и максимальное значение критерия
1 ['pss', 1, 91.62, 0.9162, 0.9162]
2 ['kld', 1, 91.1, 0.911, 0.911]
3 ['bce', 1, 91.05, 0.9105, 0.9105]
4 ['cce', 1, 90.7, 0.907, 0.907]
5 ['scce', 1, 90.66, 0.9066, 0.9066]
6 ['mk', 1, 90.59, 0.9059, 0.9059]
7 ['ck', 1, 90.23, 0.9023, 0.9023]
8 ['sh', 1, 90.14, 0.9014, 0.9014]
9 ['mse', 1, 90.08, 0.9008, 0.9008]
10 ['lc', 1, 89.15, 0.8915, 0.8915]
11 ['msle', 1, 89.11, 0.8911, 0.8911]
12 ['ch', 1, 88.81, 0.8881, 0.8881]
13 ['mae', 1, 85.09, 0.8509, 0.8509]
14 ['mape', 1, 69.2, 0.692, 0.692]
15 ['h', 1, 69.06, 0.6906, 0.6906]
16 ['cp', 1, 5.0, 0.05, 0.05]

П6.5. Анализ одной (заданной) функции потерь

Выполняется, если oneFun = True. ФП задается параметром oneFunName, например, oneFunName = 'mse'.
Пример результата при параметрах
dataSetNum = 3, one_dir = 1, sva = 1, show_all_best = True, lossFunsEffDiag = False, print_details = False, print_funs_list = False, w_dir = 'res_net', mean_eff_all = True, oneFun = True, oneFunName = 'mse', findLossesDif = False:
Обработка историй обучения с одной функцией потерь: mse
Критерий: точность на тестовой выборке; sva = 1
Число сканируемых директорий: 15
Набор данных: CIFAR10
Всего изображений: 60000
Всего потенциально лучших решений: 1
Лучшие решения:
1.
['val_acc_CIFAR10_mse_Adam_cRNv1_3_16_BR_SH_DA.txt', 'res_net', 'mse']
Всего эпох: 200
Описание эпох:
('Эпоха лучшего решения по val_acc:', 138, 95.212, 90.08, 0.013116, 0.020777, 3386)
('Эпоха наибольшей точности acc:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
('Эпоха наименьших loss-потерь:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
('Эпоха наименьших val_loss-потерь:', 178, 95.592, 89.79, 0.012339, 0.020642, 3225)
('Эпоха с наименьшим err_img:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
Значение критериев acc и err_img для лучших решений по val_acc
['res_net', 'mse', 95.212, 3385.9999999999927]
Нижняя граница критерия: 0.0
Список эффективных функций потерь по критерию val_acc:
Элемент списка содержит: имя ФП, среднее, минимальное и максимальное значение критерия
1 ['mse', 1, 90.08, 0.9008, 0.9008]
Пример результата при параметрах
dataSetNum = 3, one_dir = 0, sva = 1, show_all_best = False, lossFunsEffDiag = False, print_details = False, print_funs_list = False, mean_eff_all = True, oneFun = True, oneFunName = 'mse':
Обработка историй обучения с одной функцией потерь: mse
Критерий: точность на тестовой выборке; sva = 1
Число сканируемых директорий: 15
Набор данных: CIFAR10
Всего изображений: 60000
Всего потенциально лучших решений: 15
Лучшие решения:
1.
['val_acc_CIFAR10_mse_Adam_cRNv1_3_16_BR_SH_DA.txt', 'res_net', 'mse']
Всего эпох: 200
Описание эпох:
('Эпоха лучшего решения по val_acc:', 138, 95.212, 90.08, 0.013116, 0.020777, 3386)
('Эпоха наибольшей точности acc:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
('Эпоха наименьших loss-потерь:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
('Эпоха наименьших val_loss-потерь:', 178, 95.592, 89.79, 0.012339, 0.020642, 3225)
('Эпоха с наименьшим err_img:', 191, 95.77, 89.66, 0.012139, 0.020692, 3149)
...
15.
['val_acc_CIFAR10_mse_Adam_c7.txt', 'w_7', 'mse']
Всего эпох: 120
Описание эпох:
('Эпоха лучшего решения по val_acc:', 55, 88.838, 69.6, 0.017151, 0.044877, 8622)
('Эпоха наибольшей точности acc:', 119, 93.414, 67.97, 0.010301, 0.050312, 6497)
('Эпоха наименьших loss-потерь:', 119, 93.414, 67.97, 0.010301, 0.050312, 6497)
('Эпоха наименьших val_loss-потерь:', 29, 80.582, 69.55, 0.028417, 0.04232, 12754)
('Эпоха с наименьшим err_img:', 115, 93.356, 68.89, 0.010384, 0.049103, 6434)
Значение критериев acc и err_img для лучших решений по val_acc
['3w_14', 'mse', 98.1679999961853, 3436.0000019073414]
['2w_14', 'mse', 97.8399999961853, 3689.000001907356]
['w_14', 'mse', 96.38799999999999, 4567.0]
['w_15', 'mse', 96.3799999961853, 4426.000001907349]
['w_18', 'mse', 96.09999999809266, 4412.000000953674]
['res_net', 'mse', 95.212, 3385.9999999999927]
['w_34', 'mse', 95.18799999999999, 4227.000000000007]
['w_13', 'mse', 93.672, 5944.0]
['w_4', 'mse', 93.4800000038147, 6263.999998092651]
['w_33', 'mse', 92.6740000038147, 6139.999998092651]
['w_1', 'mse', 91.9920000038147, 6411.999998092651]
['w_17', 'mse', 91.3499999961853, 6921.000001907341]
['w_7', 'mse', 88.83799999809266, 8621.000000953674]
['w_5', 'mse', 84.98400000190735, 10422.999999046326]
['w_16', 'mse', 74.274, 15400.0]
Нижняя граница критерия: 0.0
Список эффективных функций потерь по критерию val_acc:
Элемент списка содержит: имя ФП, среднее, минимальное и максимальное значение критерия
1 ['mse', 15, 74.97, 0.696, 0.9008]

П6.6. Поиск лучших решений

Выполняется, если mean_eff_all = False и oneFun = False. При этом важно правильно задать список low_level.
Например, в случае CIFAR-10 low_level = [60000 - 2000, 0.8, 0.95].
Дополнительно при mean_eff_all = False и oneFun = False дается описание следующих решений:
- решение с наибольшей разницей между номерами эпох обновления максимума заданного критерия;
- решение с наибольшей разницей между значениями двух последних обновлений максимума заданного критерия;
Пример результата при параметрах
dataSetNum = 3, one_dir = 0, sva = 1, show_all_best = False, lossFunsEffDiag = False, print_details = False, print_funs_list = False, mean_eff_all = False, oneFun = False, low_level[1] = 0.8, findLossesDif = False:
Критерий: точность на тестовой выборке; sva = 1
Число сканируемых директорий: 15
Набор данных: CIFAR10
Всего изображений: 60000

Решение с наибольшей разницей между номерами эпох обновления максимума критерия val_acc
Замечание. В случае corr_img выводится err_img = all_img - corr_img
Модель: res_net
Функция потерь: lc
Всего эпох в решении: 200
Максимум критерия в решении: 0.8915
Эпоха максимума критерия: 157
Расстояние между эпохами / критериями: 54 / 0.07699999999999996
Эпоха обновления максимума / значение критерия в этой эпохе: 28 / 0.7647
Эпоха следующего обновления максимума / значение критерия в этой эпохе: 82 / 0.8417

Решение с наибольшей разницей между значениями двух последних обновлений максимума критерия val_acc
Замечание. В случае corr_img выводится err_img = all_img - corr_img
Модель: w_34
Функция потерь: lc
Всего эпох в решении: 120
Максимум критерия в решении: 0.8148
Эпоха максимума критерия: 97
Расстояние между эпохами / критериями: 25 / 0.0024999999999999467
Эпоха обновления максимума / значение критерия в этой эпохе: 72 / 0.8123
Эпоха следующего обновления максимума / значение критерия в этой эпохе: 97 / 0.8148
Всего потенциально лучших решений: 23
Первое лучшее решение:
1.
['val_acc_CIFAR10_pss_Adam_cRNv1_3_16_BR_SH_DA.txt', 'res_net', 'pss']
Всего эпох: 200
Описание эпох:
('Эпоха лучшего решения по val_acc:', 155, 98.264, 91.62, 0.117225, 0.143267, 1706)
('Эпоха наибольшей точности acc:', 184, 98.436, 91.48, 0.116746, 0.143488, 1634)
('Эпоха наименьших loss-потерь:', 189, 98.412, 91.45, 0.116686, 0.143451, 1649)
('Эпоха наименьших val_loss-потерь:', 137, 97.954, 91.39, 0.118329, 0.142858, 1885)
('Эпоха с наименьшим err_img:', 184, 98.436, 91.48, 0.116746, 0.143488, 1634)
Нижняя граница критерия: 0.8
Список эффективных функций потерь по критерию val_acc:
Элемент списка содержит: имя ФП , число присутствий в СЛР, среднее, минимальное и максимальное значение критерия
1 ['pss', 2, 87.02, 0.8243, 0.9162]
2 ['kld', 2, 86.72, 0.8233, 0.911]
3 ['mk', 2, 86.52, 0.8244, 0.9059]
4 ['cce', 2, 86.14, 0.8157, 0.907]
5 ['ck', 2, 86.09, 0.8195, 0.9023]
6 ['bce', 2, 85.96, 0.8087, 0.9105]
7 ['mse', 2, 85.94, 0.8179, 0.9008]
8 ['scce', 2, 85.88, 0.811, 0.9066]
9 ['msle', 2, 85.34, 0.8158, 0.8911]
10 ['lc', 2, 85.31, 0.8148, 0.8915]
11 ['sh', 1, 90.14, 0.9014, 0.9014]
12 ['ch', 1, 88.81, 0.8881, 0.8881]
13 ['mae', 1, 85.09, 0.8509, 0.8509]

П6.7. Некоторые характеристики обучения

Выводятся, если findLossesDif = True.
Вычисляются и выводятся следующие показатели:
- число совпадений лучших решений с решением с наименьшими потерями (в %);
- среднее относительное отклонение по потерям между лучшим решением и решением с наименьшими потерями (в %).
Пример результата при параметрах
dataSetNum = 3, one_dir = 0, sva = 1, show_all_best = False, lossFunsEffDiag = False, print_details = False, print_funs_list = False, mean_eff_all = False, oneFun = False, low_level[1] = 0.8, findLossesDif = True:
...
Всего решений: 23
Число совпадений лучших решений с решением с наименьшими потерями, %: 8.695652173913043
Среднее относительное отклонения по потерям между лучшими решениями и решениями с наименьшими потерями loss, %: 4.869357981407588
...
Остальное, как в П6.6.

П6.8. Вывод графиков обучения

Графики точности классификации и потерь выводятся, если plot_acc_loss = True.
При этом будут выведены графики точности (рис. П1, а), если is_acc = True; в противном случае будут показаны графики потерь (рис. П1, б).
Принудительно, если plot_acc_loss = True, устанавливается showFreq = showFreqDiag = showDiag2 = False.

CIFAR-10

Рис. П1. Примеры графиков обучения: а – точность; б – потери

П6.9. Диаграмма потенциально лучших решений

Выводится, если bestResultsDiag = True, а lossFunsEffDiag = False.
Число выводимых решений ограничивается переменной n_to_show.
Вывод возможен при one_dir = 0 или one_dir = 1.
Кроме того, нужно задать plot_acc_loss = False, oneFun = False.
Также нужно правильно задать список low_level.
Параметры mean_eff_all, showFreq не влияют на результат.
Для повышения наглядности следует правильно указать коэффициенты в следующих выражениях процедуры plotBestResultsDiag:
accMin = 0.99 * min(accV)
accMax = 1.01 * max(accV)
На рис. П2 показана диаграмма обучения 15 НС на CIFAR-10; критерий: val_acc.

CIFAR-10

Рис. П2. Диаграмма потенциально лучших решений

П6.10. Диаграмма частоты появления функций в списках эффективных функций моделей НС

Выводится, если lossFunsEffDiag = True.
Кроме того, нужно задать plot_acc_loss = False, one_dir = 2, oneFun = False, mean_eff_all = False, showFreq = True.
Важно правильно задать список low_level.
Диаграмму (рис. П3) имеет смысл строить, если mean_eff_all = False, в противном случае все столбики будут одной высоты.

CIFAR-10

Рис. П3. Частота появления ФП в списке лучших решений (вариант 1)

Такая же диаграмма (рис. П4) будет выведена при plot_acc_loss = False, one_dir = 2, oneFun = False, mean_eff_all = False, showFreqDiag = True.

CIFAR-10

Рис. П4. Частота появления ФП в списке лучших решений (вариант 2)

П6.11. Диаграмма эффективности функций потерь

Выводится, если lossFunsEffDiag = True и showDiag2 = True.
Кроме того, нужно задать plot_acc_loss = False, one_dir = 0 или 1, oneFun = False, mean_eff_all = True или False.
Если one_dir = 1, то принудительно устанавливается mean_eff_all = True :

if one_dir == 1: mean_eff_all = True

Важно правильно задать список low_level.
Если mean_eff_all = False, то диаграмма строится по потенциально лучшим решениям (рис. П5); в противном случае – по всем.

CIFAR-10

Рис. П5. Диаграмма эффективности функций потерь по лучшим решениям

П6.12. Дополнительные характеристики истории обучения

Регулируются параметром plot_history_values, когда show_all_best = False, one_dir < 2 и mean_eff_all = True.
Чтобы выводить одну диаграмму следует задать lossFunsEffDiag = False и bestResultsDiag = False.
Выводятся следующие диаграммы, когда plot_history_values > 0:
plot_history_values = 1 – диаграмма расстояний между эпохами последовательного обновления максимума критерия (рис. П6); в заголовке диаграммы указывается имя ФП с наибольшим расстоянием;
plot_history_values = 2 – диаграмма разницы между двумя последними обновлениями максимума критерия (рис. П7);
plot_history_values = 3 – диаграмма числа обновлений максимума критерия (рис. П8).

CIFAR-10

Рис. П6. Диаграмма расстояний между эпохами последовательного обновления максимума val_acc

CIFAR-10

Рис. П7. Диаграмма разницы между двумя последними обновлениями максимума val_acc

CIFAR-10

Рис. П8. Диаграмма числа обновлений максимума val_acc

П6.13. Вывод пар моделей с близкими списками эффективных функций потерь

Два списка эффективных ФП считаются близкими, если число совпадающих ФП в этих списках не менее near %.
Поиск таких списков выполняется, если:
near > 0 (коэффициент близости списков эффективных функций потерь моделей НС);
one_dir = 2 (сканирование директорий по одной);
mean_eff_all = False (поиск по всем ФП).

П6.14. Вывод списка эффективных моделей

Выводится, когда print_eff_models_list = True. Работает только, когда one_dir = 0.
При э том принудительно выполняется:
if one_dir == 1: print_eff_models_list = False
Модель считается эффективной, если в ней есть хотя бы одно решение с критерием, большим чем его нижняя граница, равная low_level[sva].
Для разных значений sva модем получить разные списки эффективных моделей.
После вывода списка можно изменить all_dirs, оставив в нем только эффективные модели.

Приложение 7. Код создания и обучения сети ResNet

import numpy as np
import time
import sys # Для sys.exit()
import tensorflow as tf
#
lossFunNum = 14
dataSetNum = 3 # 1 - MNIST; 2 - EMNIST; 3 - CIFAR-10; 4 - CIFAR-100
shuffle = True # True False
useBN = True # True False
#
tst = False
gpu = False # True False
tpu = False
if gpu: tpu = False
version = 1
n_in_res_net = 5 if version == 1 else 2 # Число блоков в НС
num_filters_0 = 16
res_rate = 0 # 0.2
if dataSetNum == 3:
    epochs = 200 # CIFAR10
    batch_size = 128
    subtract_pixel_mean = True
    # Флаг использования сгенерированных данных (data augmentation в режиме реального времени)
    use_d_a = True # True False
    patience = 50
    k_size = 3 # Размер окна фильтра
    pool_size = 8 # Размер окна подвыборки
else:
    epochs = 120
    batch_size = 256
    subtract_pixel_mean = False
    use_d_a = False
    patience = 30
    k_size = 4
    pool_size = 7
# Флаг, определяющий положение слоев BatchNormalization и Activation (перед или после сверточного слоя)
BA_first = False # True False
# Флаг использование Reshape в быстрой ветви блока
useRA = False
if dataSetNum < 3: useRA = False
# Флаг совместного использования BatchNormalization и Activation
BN_ReLU = True
vs = 'v' + str(version) + '_'
nir = str(n_in_res_net) + '_'
nf0 = str(num_filters_0)
ba = '_BA' if BA_first else ''
ra = '_RA' if useRA else ''
br = '_BR' if BN_ReLU else ''
sh = '_SH' if shuffle else ''
rt = str(res_rate) if res_rate > 0 else ''
modelNum = 'RN' + vs + nir + nf0 + ba + ra + br + sh + rt
# Флаг задания режима воспроизведения результатов
seedVal = 348
np.random.seed(seedVal) # Задание затравки датчика случайных чисел
if tst: epochs = 4
#
if gpu or tpu:
    import os
    from tensorflow.keras.datasets import cifar10
    from tensorflow.keras.datasets import mnist
    import tensorflow.keras.losses as ls
    import tensorflow.keras.metrics as mt
    import tensorflow.keras.callbacks as cb
    import tensorflow.keras.utils as ut
    from tensorflow.keras import backend as K
    from tensorflow.keras.models import Model
    from tensorflow.keras.layers import Input, Dense, Dropout, average, Activation
    from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Reshape
    from tensorflow.keras.layers import BatchNormalization, add, AveragePooling2D
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras import initializers
    from tensorflow.keras.regularizers import l2 # l1
    from tensorflow.keras.layers import LSTM
    from tensorflow.keras.preprocessing.image import ImageDataGenerator
    from google.colab import files
    from google.colab import drive
else:
    import keras
    import keras.losses as ls
    import keras.metrics as mt
    import keras.callbacks as cb
    import keras.utils as ut
    from keras import backend as K
    from keras.models import Model
    from keras.layers import Input, Dense, Dropout, average, Activation
    from keras.layers import Conv2D, MaxPooling2D, Flatten, Reshape
    from keras.layers import BatchNormalization, add, AveragePooling2D
    from keras.optimizers import Adam
    from keras import initializers
    from keras.regularizers import l2 # l1
    from keras.preprocessing.image import ImageDataGenerator
    from keras.utils import plot_model
    from loadSaveShow import makeNames, load_cifar10, load_cifar100
    from lossFuns import myLoss
#
# Сумма двух функций потерь (CCE + KLD)
def cce_kld(y_true, y_pred):
    cce = K.categorical_crossentropy(y_true, y_pred)
    y_true = K.clip(y_true, K.epsilon(), 1)
    y_pred = K.clip(y_pred, K.epsilon(), 1)
    kld = K.sum(y_true * K.log(y_true / y_pred), axis = -1)
    return cce + kld
#
# Сумма двух функций потерь (17 * MSE + KLD)
def mse17_kld(y_true, y_pred):
    mse = K.mean(K.square(y_pred - y_true), axis = -1)
    y_true = K.clip(y_true, K.epsilon(), 1)
    y_pred = K.clip(y_pred, K.epsilon(), 1)
    kld = K.sum(y_true * K.log(y_true / y_pred), axis = -1)
    return 17.0 * mse + kld
#
def cosine_proximity(y_true, y_pred):
    y_true = K.l2_normalize(y_true, axis=-1)
    y_pred = K.l2_normalize(y_pred, axis=-1)
    return -K.sum(y_true * y_pred, axis=-1)
#
monitorES = 'val_acc'
#
if lossFunNum == 1: loss = ls.mean_squared_error
elif lossFunNum == 2: loss = ls.mean_absolute_error
elif lossFunNum == 3: loss = ls.mean_absolute_percentage_error
elif lossFunNum == 4: loss = ls.mean_squared_logarithmic_error
elif lossFunNum == 5: loss = ls.squared_hinge
elif lossFunNum == 6: loss = ls.hinge
elif lossFunNum == 7: loss = ls.categorical_hinge
elif lossFunNum == 8: loss = ls.logcosh
elif lossFunNum == 9: loss = ls.categorical_crossentropy
elif lossFunNum == 10: loss = ls.sparse_categorical_crossentropy
elif lossFunNum == 11: loss = ls.binary_crossentropy
elif lossFunNum == 12: loss = ls.kullback_leibler_divergence
elif lossFunNum == 13: loss = ls.poisson
elif lossFunNum == 14: loss = cosine_proximity # ls.Huber()
elif lossFunNum == 15: loss = myLoss
elif lossFunNum == 16: loss = cce_kld
elif lossFunNum == 17: loss = mse17_kld
#
# Путь к данным
if gpu or tpu:
    drv = '/content/drive/'
    drive.mount(drv)
    pathToData = drv + 'My Drive/'
else:
    p0 = 'G:/AM/НС/'
    if dataSetNum == 1: pathToData = p0 + 'mnist/'
    elif dataSetNum == 2: pathToData = p0 + 'emnist/'
    elif dataSetNum == 3: pathToData = p0 + 'cifar10/'
    elif dataSetNum == 4: pathToData = p0 + 'cifar100/'
#
if dataSetNum == 1 or dataSetNum == 3: # MNIST или CIFAR-10
    num_classes = 10
elif dataSetNum == 2: # EMNIST
    num_classes = 26
elif dataSetNum == 4: # CIFAR-100
    num_classes = 100
#
if dataSetNum >= 3: # CIFAR-10, CIFAR-100
    img_rows = img_cols = 32
    img_size = img_rows * img_cols * 3
else: # MNIST, EMNIST
    img_rows = img_cols = 28
    img_size = img_rows * img_cols
#
input_shape = (img_rows, img_cols, 3 if dataSetNum >= 3 else 1)
#
nnType = 'conv'
#
if dataSetNum == 1: imgType = 'MNIST'
elif dataSetNum == 2: imgType = 'EMNIST'
elif dataSetNum == 3: imgType = 'CIFAR10'
elif dataSetNum == 4: imgType = 'CIFAR100'
#
fileWeights = lossNm = optNm = suff = ''
if lossFunNum == 1: lossNm = 'mse'
elif lossFunNum == 2: lossNm = 'mae'
elif lossFunNum == 3: lossNm = 'mape'
elif lossFunNum == 4: lossNm = 'msle'
elif lossFunNum == 5: lossNm = 'sh'
elif lossFunNum == 6: lossNm = 'h'
elif lossFunNum == 7: lossNm = 'ch'
elif lossFunNum == 8: lossNm = 'lc'
elif lossFunNum == 9: lossNm = 'cce'
elif lossFunNum == 10: lossNm = 'scce'
elif lossFunNum == 11: lossNm = 'bce'
elif lossFunNum == 12: lossNm = 'kld'
elif lossFunNum == 13: lossNm = 'pss'
elif lossFunNum == 14: lossNm = 'cp' # 'hb'
elif lossFunNum == 15: lossNm = 'myLoss'
elif lossFunNum == 16: lossNm = 'ck' # cce_kld
elif lossFunNum == 17: lossNm = 'mk' # mse17_kld
categorical = False if lossFunNum == 10 else True # False для sparse_categorical_crossentropy
#
optNm = 'Adam'
kn0_init = kn0_init_out = initializers.RandomNormal(seed = seedVal)
he_init = initializers.he_normal(seed = seedVal)
pad = 'same'
mca = mt.categorical_accuracy
metrics = ['accuracy', mca] if lossFunNum == 11 else ['accuracy']
#
def lr_schedule(epoch):
    lr = 1e-3
##        if epoch > 280:    # 180
##            lr *= 0.5e-3
##        elif epoch > 260: # 160
##            lr *= 1e-3
##        elif epoch > 160: # 120
##            lr *= 1e-2
##        elif epoch > 120: # 80
##            lr *= 1e-1
    #
    if epoch > 180:
        lr *= 0.5e-3
    elif epoch > 160:
        lr *= 1e-3
    elif epoch > 120:
        lr *= 1e-2
    elif epoch > 80: # 80
        lr *= 1e-1
    #
    print('Скорость обучения:', lr)
    return lr
#
def BN_A(x, batch_normalization, activation):
    if batch_normalization: x = BatchNormalization()(x)
    if activation is not None: x = Activation(activation)(x)
    return x
#
def resnet_layer(inputs, num_filters = 16, kernel_size = 3,
                 strides = 1, activation = 'relu',
                 batch_normalization = True,
                 BA_first = False, BN_ReLU = True):
    #
    conv = Conv2D(num_filters, kernel_size = kernel_size,
                 strides = strides, padding = pad,
                 kernel_initializer = he_init,
                 kernel_regularizer = l2(1e-4))
    x = inputs
    #
    if BA_first: # Если прежде сверточный слой
        if BN_ReLU:
            x = BN_A(x, batch_normalization, activation)
            x = conv(x)
        else:
            if batch_normalization: x = BatchNormalization()(x)
            x = conv(x)
            if activation is not None: x = Activation(activation)(x)
    else:
        x = conv(x)
        x = BN_A(x, batch_normalization, activation)
    #
    if res_rate > 0: x = Dropout(res_rate, seed = seedVal)(x)
    return x
#
def resnet_v1(input_shape, depth):
    if (depth - 2) % 6 != 0:
        raise ValueError('Величина depth - это 6n + 2 (например: 20, 32, 44)')
    # Начало формирования модели
    num_filters = num_filters_0
    num_res_blocks = int((depth - 2) / 6)
    #
    inputs = Input(shape = input_shape)
    x = resnet_layer(inputs = inputs, num_filters = num_filters,
                     batch_normalization = useBN, BA_first = BA_first,
                     BN_ReLU = BN_ReLU)
    # Создание стека слоев
    for stack in range(3):
        for res_block in range(num_res_blocks):
            strides = 1
            if stack > 0 and res_block == 0: strides = 2 # Если первый слой блока (кроме первого блока)
            y = resnet_layer(inputs = x, num_filters = num_filters, strides = strides,
                             batch_normalization = useBN, BA_first = BA_first,
                             BN_ReLU = BN_ReLU)
            y = resnet_layer(inputs = y, num_filters = num_filters, activation = None,
                             batch_normalization = useBN, BA_first = BA_first,
                             BN_ReLU = BN_ReLU)
            if stack > 0 and res_block == 0: # Если первый слой блока (кроме первого блока)
                if useRA:
                    if stack == 1:
                        x = Reshape((16, 32, num_filters))(x)
                        x = AveragePooling2D(pool_size = (1, 2))(x)
                    elif stack == 2:
                        x = Reshape((8, 16, num_filters))(x)
                        x = AveragePooling2D(pool_size = (1, 2))(x)
                else:
                    x = resnet_layer(inputs = x, num_filters = num_filters,
                                     kernel_size = 1, strides = strides,
                                     activation = None, batch_normalization = False)
            x = add([x, y])
            x = Activation('relu')(x)
        num_filters *= 2
    # Добавляем классификатор (Dense)
    x = AveragePooling2D(pool_size = pool_size)(x)
    y = Flatten()(x)
    outputs = Dense(num_classes, activation = 'softmax', kernel_initializer = he_init)(y)
    # Создаем модель
    model = Model(inputs = inputs, outputs = outputs)
    return model
#
def resnet_v2(input_shape, depth):
    if (depth - 2) % 9 != 0:
        raise ValueError('Глубина должна равняться 9n + 2 (например, 56 или 110)')
    num_filters_in = 16
    num_res_blocks = int((depth - 2) / 9)
    inputs = Input(shape = input_shape)
    # В v2 на входе перед развилкой вводится Conv2D с BN-ReLU
    x = resnet_layer(inputs = inputs, num_filters = num_filters_in, BA_first = True)
    # Создаем стек - кусок сети
    for stage in range(3):
        for res_block in range(num_res_blocks):
            activation = 'relu'
            batch_normalization = True
            strides = 1
            if stage == 0:
                num_filters_out = num_filters_in * 4
                if res_block == 0: # Первый слой и первый кусок (этап)
                    activation = None
                    batch_normalization = False
            else:
                num_filters_out = num_filters_in * 2
                # Первый слой, но не первый этап
                if res_block == 0: strides = 2 # Субдискретизация
            # Верхний слой
            y = resnet_layer(inputs = x,
                             num_filters = num_filters_in,
                             kernel_size = 1,
                             strides = strides,
                             activation = activation,
                             batch_normalization = batch_normalization,
                             BA_first = BA_first)
            y = resnet_layer(inputs = y,
                             num_filters = num_filters_in,
                             BA_first = BA_first)
            y = resnet_layer(inputs=y,
                             num_filters = num_filters_out,
                             kernel_size = 1,
                             BA_first = BA_first)
            if res_block == 0:
                # Вводим слой в передаточной ветви для согласования форм
                x = resnet_layer(inputs = x,
                                 num_filters = num_filters_out,
                                 kernel_size = 1,
                                 strides = strides,
                                 activation = None,
                                 batch_normalization = False)
            x = add([x, y])
        num_filters_in = num_filters_out
    # Добавляем классиификатор
    # Добавим в v2 перед Pooling слои BN и ReLU
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = AveragePooling2D(pool_size = 8)(x)
    y = Flatten()(x)
    outputs = Dense(num_classes, activation = 'softmax', kernel_initializer = 'he_normal')(y)
    model = Model(inputs = inputs, outputs = outputs)
    return model
#
# Загрузка EMNIST
def load_data(allParams):
    def loadBinData(pathToData):
        pathToBin = pathToData + ('emnist/' if gpu or tpu else '')
        print('Загрузка данных из двоичных файлов...')
        with open(pathToBin + 'imagesTrain.bin', 'rb') as read_binary:
            data = np.fromfile(read_binary, dtype = np.uint8)
        with open(pathToBin + 'labelsTrain.bin', 'rb') as read_binary:
            labels = np.fromfile(read_binary, dtype = np.uint8)
        with open(pathToBin + 'imagesTest.bin', 'rb') as read_binary:
            data2 = np.fromfile(read_binary, dtype = np.uint8)
        with open(pathToBin + 'labelsTest.bin', 'rb') as read_binary:
            labels2 = np.fromfile(read_binary, dtype = np.uint8)
        return data, labels, data2, labels2
    #
    img_rows = allParams[6]
    img_cols = allParams[7]
    pathToData = allParams[13]
    useTestData = allParams[27]
    #
    imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData(pathToData)
    #
    x_train = np.asarray(imagesTrain)
    y_train = np.asarray(labelsTrain)
    x_test = np.asarray(imagesTest)
    y_test = np.asarray(labelsTest)
    if imgType == 'EMNIST':
        y_train -= 1
        y_test -= 1
    x_train_shape_0 = int(x_train.shape[0] / (img_rows * img_cols)) # 60000 / 124800
    x_test_shape_0 = int(x_test.shape[0] / (img_rows * img_cols)) # 10000 / 20800
    #
    # Меняем форму входных данных (обучающих и тестовых)
    if imgType == 'MNIST':
        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)
    elif imgType == 'EMNIST':
        x_train = x_train.reshape(x_train_shape_0, img_rows, img_cols, 1).transpose(0,2,1,3)
        x_test = x_test.reshape(x_test_shape_0, img_rows, img_cols, 1).transpose(0,2,1,3)
    return x_train, y_train, x_test, y_test
#
allParams = []
for k in range(34): allParams.append('')
allParams[1] = num_classes
allParams[3] = lossFunNum
allParams[6] = img_rows
allParams[7] = img_cols
allParams[13] = pathToData
allParams[14] = True # loadFromBin
allParams[19] = False # show_img
allParams[27] = True # useTestData
allParams[28] = imgType
allParams[33] = subtract_pixel_mean
if gpu or tpu:
    if dataSetNum == 1:
        (x_train, y_train), (x_test, y_test) = mnist.load_data()
        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)
    elif dataSetNum == 2:
        x_train, y_train, x_test, y_test = load_data(allParams)
    elif dataSetNum == 3:
        (x_train, y_train), (x_test, y_test) = cifar10.load_data()
    x_train = np.asarray(x_train, dtype = 'float32') / 255.0
    x_test = np.asarray(x_test, dtype = 'float32') / 255.0
    if categorical:
        y_train = ut.to_categorical(y_train, num_classes)
        y_test = ut.to_categorical(y_test, num_classes)
else:
    if dataSetNum > 2:
        if dataSetNum == 3: # 'CIFAR10'
            x_train, y_train, x_test, y_test = load_cifar10(allParams)
        else:
            x_train, y_train, x_test, y_test = load_cifar100(allParams)
    else:
        x_train, y_train, x_test, y_test = load_data(allParams)    
#
print('Форма x_train:', x_train.shape)
print(x_train.shape[0], 'обучающих примеров')
print(x_test.shape[0], 'проверочных примеров')
print('Форма y_train:', y_train.shape)
#
if subtract_pixel_mean:
    x_train_mean = np.mean(x_train, axis = 0)
    x_train -= x_train_mean
    x_test -= x_train_mean
#
input_shape = x_train.shape[1:]
depth = n_in_res_net * 6 + 2
if tpu:
    resolver = tf.distribute.cluster_resolver.TPUClusterResolver()
    tf.tpu.experimental.initialize_tpu_system(resolver)
    tpu_strategy = tf.distribute.experimental.TPUStrategy(resolver)
    with tpu_strategy.scope():
        if version == 1:
            model = resnet_v1(input_shape = input_shape, depth = depth)
        else:
            depth = n_in_res_net * 9 + 2
            model = resnet_v2(input_shape, depth)
        model.compile(optimizer = Adam(lr = lr_schedule(0)), loss = loss, metrics = metrics)
else:
    if version == 1:
        model = resnet_v1(input_shape = input_shape, depth = depth)
    else:
        depth = n_in_res_net * 9 + 2
        model = resnet_v2(input_shape, depth)
    model.compile(optimizer = Adam(lr = lr_schedule(0)), loss = loss, metrics = metrics)
##model.summary()
##sys.exit()
#
d_a = '_DA' if use_d_a else ''
suff = imgType + '_' + lossNm + '_' + optNm + '_' + nnType + modelNum + d_a
print('Решение:', suff)
if useBN: print('Используется batch-нормализация')
print('Размер пакета обучения:', batch_size)
if shuffle: print('Мешаем данные пакета обучения')
start_time = time.time()
#
filesToSave = 'weights_' + suff + '.{epoch:03d}-{val_acc:.2f}.hdf5'
pathWeights = pathToData + filesToSave
monitor = 'val_categorical_accuracy' if lossFunNum == 11 else 'val_acc'
if gpu or tpu:
    checkpoint = cb.ModelCheckpoint(pathWeights, monitor = monitor, verbose = 0,
                                    save_weights_only = True,
                                    save_best_only = True, mode = 'max', save_freq = 1)
else:
    checkpoint = cb.ModelCheckpoint(pathWeights, monitor = monitor, verbose = 0,
                                    save_weights_only = True,
                                    save_best_only = True, mode = 'max', period = 1)
# Список функций обратного вызова
callbacks_list = []
# Функция обратного вызова, задающая график изменения скорости обучения
lr_scheduler = cb.LearningRateScheduler(lr_schedule)
# Функция обратного вызова, снижающая скорость обучения,
# если потери val_loss не снижаются patience эпох
lr_reducer = cb.ReduceLROnPlateau(factor = np.sqrt(0.1), cooldown = 0, patience = 5, min_lr = 0.5e-6)
callbacks_list.append(lr_reducer)
callbacks_list.append(lr_scheduler)
#
print('Сохраняем веса в файлы вида ' + filesToSave)
print('Используется ранняя остановка по метрике', monitorES, 'с параметром patience =', patience)
#
if tpu:
    history = model.fit(x_train, y_train, epochs = epochs, steps_per_epoch = 50, verbose = 2,
                        validation_data = (x_test, y_test), validation_freq = epochs)
else:
    if not use_d_a:
        #callbacks_list.append(checkpoint)
        #callbacks_list.append(cb.EarlyStopping(monitor = monitorES, patience = patience))
        history = model.fit(x_train, y_train, batch_size = batch_size, epochs = epochs,
                            verbose = 2, validation_data = (x_test, y_test),
                            shuffle = shuffle, callbacks = callbacks_list)
    else:
        print('Обучение по сгенерированным в режиме реального времени данным (data augmentation)')
        datagen = ImageDataGenerator(
            # Нулевое среднее значение входных данных по всему набору (False - значит не используем)
            featurewise_center = False,
            # Нулевое среднее значение по каждому экземпляру данных (каждому изображению)
            samplewise_center = False,
            # Делим вход на его сренеквадратическое значение
            featurewise_std_normalization = False,
            # Делим каждый экземпляр (изображение) на его сренеквадратическое значение
            samplewise_std_normalization = False,
            # ZCA-отбеливание
            zca_whitening = False,
            # epsilon для ZCA
            zca_epsilon = 1e-06,
            # Угол (в градусах) случайного поворота изображения; берется из диапазона (0 - 180)
            rotation_range = 0,
            # Случайный горизонтальный сдвиг изображения
            width_shift_range = 0.1,
            # Случайный вертикальный сдвиг изображения
            height_shift_range = 0.1,
            # Диапазон сдвига пикселей изображения (угол сдвига в градусах в направлении против часовой стрелки)
            shear_range = 0.,
            # Диапазон случайного выбора масштабирования изображения
            zoom_range = 0.,
            # set range for random channel shifts
            channel_shift_range = 0.,
            # Способ заполнения точек за пределами границы входных изображений
            fill_mode = 'nearest',
            # Значение для точек за пределами границы изображения (используется, когда fill_mode = 'constant')
            cval = 0.,
            # Если True, то выполняется случайный горизонтальный флип изображений
            # (поворот относительно оси y на 180 градусов; ось проходит через центр изображения)
            horizontal_flip = True,
            # То же для вертикального флипа
            vertical_flip = False,
            # Показатель масштабирования данных (применяется после всех прочих преобразований)
            rescale = None,
            # Функция, которая будет применена к каждому изображению
            preprocessing_function = None,
            # Формат данных ('channels_first' или 'channels_last')
            data_format = None,
            # Доля изображений, резервируемая для оценки качества модели; выбирается из диапазона (0 - 1)
            validation_split = 0.0)
        #
        # Вычисляем величины, необходимые для нормализации
        # (std, mean и principal components, если используется ZCA)
        datagen.fit(x_train)
        # Обучаем модель на данных, сгенерированных datagen.flow()
        history = model.fit_generator(datagen.flow(x_train, y_train, batch_size = batch_size),
                                     validation_data = (x_test, y_test),
                                     epochs = epochs, verbose = 2,
                                     workers = 1, use_multiprocessing = True, callbacks = callbacks_list)
print('Время обучения:', (time.time() - start_time))
#
suff += '.txt'
f_loss = 'loss_' + suff
f_acc = 'acc_' + suff
f_val_loss = 'val_loss_' + suff
f_val_acc = 'val_acc_' + suff
# Вывод истории в файлы
print('История сохранена в файлы:\n' + f_loss + '\n' + f_acc + '\n' + f_val_loss + '\n' + f_val_acc)
if lossFunNum == 11: # binary_crossentropy
    acc = 'categorical_accuracy'
    val_acc = 'val_categorical_accuracy'
else:
    acc = 'acc'
    val_acc = 'val_acc'
hss = history.history
with open(pathToData + f_loss, 'w') as fl:
    for val in hss['loss']: fl.write(str(val) + '\r\n')
with open(pathToData + f_acc, 'w') as fl:
    for val in hss[acc]: fl.write(str(val) + '\r\n')
with open(pathToData + f_val_loss, 'w') as fl:
    for val in hss['val_loss']: fl.write(str(val) + '\r\n')
with open(pathToData + f_val_acc, 'w') as fl:
    for val in hss[val_acc]: fl.write(str(val) + '\r\n')
##if gpu or tpu:
##    files.download(pathToData + f_loss)
##    files.download(pathToData + f_acc)
##    files.download(pathToData + f_val_loss)
##    files.download(pathToData + f_val_acc)

Список работ

Рейтинг@Mail.ru