Список работ

Классификация графических образов сверточной нейронной сетью

Содержание

Введение

Рассматривается задача создания нейронных сетей (НС) для классификация графических образов, хранимых наборам данных MNIST [1], EMNIST-Letters [2] и CIFAR-10 [3].
НС реализуются на языке программирования Python средствами библиотеки Keras.
Во всех случаях используется сверточные нейронные сеть (СНС), функция потерь Расстояние Кульбака-Лейблера (kullback leibler divergence) и метод оптимизации Adam.

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

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

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

MNIST

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

EMNIST

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

CIFAR-10

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

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

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

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

from mnist import MNIST
mndata = MNIST(path)
# Разрешаем чтение архивированных данных
mndata.gz = True
# Обучающая выборка (данные и метки)
imagesTrain, labelsTrain = mndata.load_training()
# Тестовая выборка (данные и метки)
imagesTest, labelsTest = mndata.load_testing()
fn = open('imagesTrain.bin', 'wb')
fn2 = open('labelsTrain.bin', 'wb')
fn3 = open('imagesTest.bin', 'wb')
fn4 = open('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("All MNIST files saved")

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

def loadBinData(fn, fn2, fn3, fn4):
    with open(fn, 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn2, 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn3, 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn4, 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    return data, labels, data2, labels2
#
imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData('imagesTrain.bin', 'labelsTrain.bin', 'imagesTest.bin', 'labelsTest.bin')
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(60000, img_rows, img_cols, 1)
X_test = X_test.reshape(10000, img_rows, img_cols, 1)

Описание сверточных нейронных сетей

Для работы с MNIST, EMNIST-Letters и CIFAR-10 используется одна и та же архитектура СНС (рис. 5).

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

Рис. 5. Архитектура сверточной нейронной сети для MNIST, EMNIST-Letters и CIFAR-10

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

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

Во всех сверточных слоях использована функция активации ReLU, первый полносвязный слой так же снабжен функцией активации ReLU, второй – Linear, а третий – Softmax.
Описание слоев СНС для MNIST и EMNIST-Letters приведено в табл. 1.

Таблица 1. Описание слоев СНС для MNIST и EMNIST-Letters

Layer (type)Output ShapeParam #
input_1 (InputLayer)(None, 28, 28, 1)0
conv2d_1 (Conv2D)(None, 28, 28, 20)340
max_pooling2d_1 (MaxPooling2D)(None, 14, 14, 20)0
conv2d_2 (Conv2D)(None, 10, 10, 30)9'630
max_pooling2d_2 (MaxPooling2D)(None, 5, 5, 30)0
flatten_1 (Flatten)(None, 750)0
dense_1 (Dense)(None, 600)450'600
dense_2 (Dense)MNIST: (None, 30)18'030
EMNIST: (None, 30)18'030
dense_3 (Dense)MNIST: (None, 10)310
EMNIST: (None, 26)806
Total params (всего параметров):
MNIST: 478'910
EMNIST: 479'406
Trainable params (обучаемых параметров):
MNIST: 478'910
EMNIST: 479'406
Non-trainable params (необучаемых параметров): 0

Форма выходного слоя определяется числом классов. В MNIST их 10, а в EMNIST-Letters – 26.
В CIFAR-10 изображения цветные и имеют размер 32*32 пикселя, что определяет следующую форму входа:

input_shape = (32, 32, 3)

Описание слоев СНС для CIFAR-10 приведено в табл. 2.

Таблица 2. Описание слоев СНС для CIFAR-10

Layer (type)Output ShapeParam #
input_1 (InputLayer)(None, 32, 32, 1)0
conv2d_1 (Conv2D)(None, 32, 32, 32)896
max_pooling2d_1 (MaxPooling2D)(None, 16, 16, 32)0
conv2d_2 (Conv2D)(None, 16, 16, 64)18'496
max_pooling2d_2 (MaxPooling2D)(None, 8, 8, 64)0
flatten_1 (Flatten)(None, 4096)0
dense_1 (Dense)(None, 1024)4'195'328
dense_2 (Dense)(None, 16)16'400
dense_3 (Dense)(None, 10)170
Total params: 4'231'290 (всего параметров)
Trainable params: 4'231'290 (обучаемых параметров)
Non-trainable params: 0 (необучаемых параметров)

Реализация сети для MNIST

Код, обеспечивающий работу с MNIST:

from mnist import MNIST
import numpy as np
import time
import matplotlib.pyplot as plt
import keras
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import sys # Для sys.exit()
#
#from keras.datasets import mnist
#
def params():
    #
    # Пользовательская функция потерь. На входе тензоры
    def myLoss(y_true, y_pred):
        dist = K.sqrt(K.sqrt(K.sum(K.square(K.square(y_pred - y_true)))))
        err = dist
        return err # Вернет тензор с shape=(?,)
    #
    img_rows = img_cols = 28 # Размер входного образа
    #
    # Параметры модели
    # Число экземпляров данных обучающей выборки между пересчетами весовых коэффициентов
    # Чем меньше batch_size, тем дольше процесс обучения
    batch_size = 256
    # Число классов (по числу цифр)
    num_classes = 10
    # Число эпох обучения
    epochs = 9
    conv_filters = 20
    conv2_filters = 30
    dense_units = 600
    dense2_units = 30
    categorical = True # True; False для sparse_categorical_crossentropy
    #
    # Виды функций потерь:
    # mean_squared_error - mse;
    # mean_absolute_error - mae;
    # mean_absolute_percentage_error - mape;
    # mean_squared_logarithmic_error - msle;
    # squared_hinge - sh;
    # hinge;
    # categorical_hinge;
    # logcosh;
    # categorical_crossentropy;
    # sparse_categorical_crossentropy;
    # binary_crossentropy;
    # kullback_leibler_divergence;
    # cosine_proximity
    #
    userLoss = False # False True
    saveWeightsOnly = userLoss
    if userLoss:
        loss = myLoss
    else:
        loss = keras.losses.binary_crossentropy
    print(loss)
    #
    # Виды методов оптимизации:
    # 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)
    #
    optimizer = keras.optimizers.Adam()
    #
    allParams = []
    allParams.append(batch_size)
    allParams.append(num_classes)
    allParams.append(epochs)
    allParams.append(loss)
    allParams.append(categorical)
    allParams.append(optimizer)
    allParams.append(img_rows)
    allParams.append(img_cols)
    allParams.append(saveWeightsOnly)
    allParams.append(conv_filters)
    allParams.append(conv2_filters)
    allParams.append(dense_units)
    allParams.append(dense2_units)
    return allParams
#
def loadBinData(fn, fn2, fn3, fn4):
    with open(fn, 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn2, 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn3, 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn4, 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    return data, labels, data2, labels2
#
def load_data(path, allParams):
    num_classes = allParams[1]
    categorical = allParams[4]
    img_rows = allParams[6]
    img_cols = allParams[7]
    # Читаем в списки imagesTrain, labelsTrain, imagesTest и labelsTest данные и метки
    # из ранее скаченных файлов - набора данных MNIST
    # Данные - это преставление числа (метки) на рисунке размера 28*28 пикселей
    # Метка - это число из диапазона [0, 9]
    # В каждом пикселе представлен оттенок серого цвета. Он задается числом из диапазона [0, 255]
    loadFromBin = True
    if loadFromBin:
        imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData('imagesTrain.bin', 'labelsTrain.bin', 'imagesTest.bin', 'labelsTest.bin')
    else:
        mndata = MNIST(path)
        # Разрешаем чтение архивированных данных
        mndata.gz = True
        # Обучающая выборка (данные и метки)
        imagesTrain, labelsTrain = mndata.load_training()
        # Тестовая выборка (данные и метки)
        imagesTest, labelsTest = mndata.load_testing()
        saveData = False # True False
        if saveData:
            fn = open('imagesTrain.bin', 'wb')
            fn2 = open('labelsTrain.bin', 'wb')
            fn3 = open('imagesTest.bin', 'wb')
            fn4 = open('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("All MNIST files saved")
            sys.exit()
        #
        # При работк с MNIST данные можно ввести, используя класс mnist, определенный в keras.datasets, но это медленнее
        #(X_train, y_train), (X_test, y_test) = mnist.load_data()
    #
    # Преобразование списков в одномерные массивы
    # Каждый элемент массива - это вектор размера 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 loadFromBin:
        X_train_shape_0 = 60000
        X_test_shape_0 = 10000
    else:
        X_train_shape_0 = X_train.shape[0]
        X_test_shape_0 = X_test.shape[0]
        ##print(X_train[0, :]) # Напечатает 784 цифры, представляющих вариант рукописного числа 5
        ##print(y_train[0]) # Напечатает: 5
        ##print(X_train.shape) # (60000, 784)
        ##print(y_train.shape) # (60000,)
    # Меняем форму входных данных (обучающих и тестовых)
    if K.image_data_format() == 'channels_first':
        X_train = X_train.reshape(X_train_shape_0, 1, img_rows, img_cols)
        X_test = X_test.reshape(X_test_shape_0, 1, img_rows, img_cols)
        input_shape = (1, img_rows, img_cols)
    else: # channels_last
        X_train = X_train.reshape(X_train_shape_0, img_rows, img_cols, 1)
        X_test = X_test.reshape(X_test_shape_0, img_rows, img_cols, 1)
        input_shape = (img_rows, img_cols, 1)
    # Реально имеем channels_last. Но можно изменить: K.set_image_data_format('channels_first')
    #
    show = False # False True
    if show:
        print(X_train.shape) # (60000, 28, 28, 1)
        print(y_train.shape) # (60000,)
        print(X_test.shape) # (10000, 28, 28, 1)
        print(y_test.shape) # (10000,)
        # Выводим 9 изображений обучающего или тестового набора
        lets = []
        for i in range(10): lets.append(chr(48 + i)) # ['0', '1', '2', ..., '9']
        test = False # True or False
        for i in range(9):
            k = i
            plt.subplot(3, 3, i + 1)
            ind = y_test[k] if test else y_train[k]
            let = X_test[k][0:img_rows, 0:img_rows, 0] if test else X_train[k][0:img_rows, 0:img_rows, 0]
            fig = plt.imshow(let, cmap = plt.get_cmap('gray'))
            plt.title(lets[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[0, :, :, 0]) # Напечатает массив размера 28*28
    #
    # Приведение к диапазону [0.0, 1.0]
    X_train /= 255
    X_test /= 255
    #
    # Преобразование метки - числа из диапазона [0, 9] в двоичный вектор размера 10
    # Так, метка 5 (соответствует цифре 5) будет преобразована в вектор [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
    if categorical:
        print(y_train[0]) # Напечатает: 5
        y_train = keras.utils.to_categorical(y_train, num_classes)
        y_test = keras.utils.to_categorical(y_test, num_classes)
        print(y_train[0]) # Напечатает: [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
    #
    print('Форма X_train:', X_train.shape) # Напечатает: Форма X_train: (60000, 28, 28, 1)
    print(X_train.shape[0], 'обучающих образов') # Напечатает: 60000 обучающих образов
    print(X_test.shape[0], 'тестовых образов') # Напечатает: 10000 тестовых образов
    #
    return X_train, y_train, X_test, y_test, input_shape
#
def createModel(input_shape, allParams):
    num_classes = allParams[1]
    loss = allParams[3]
    categorical = allParams[4]
    optimizer = allParams[5]
    conv_Filters = allParams[9]
    conv2_Filters = allParams[10]
    dense_units = allParams[11]
    dense2_units = allParams[12]
    #
    visible = Input(shape = input_shape)
    conv = Conv2D(conv_Filters, kernel_size = (4, 4), strides = (1, 1), padding = 'same', activation = 'relu')(visible)
    pool = MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same')(conv)
    conv2 = Conv2D(conv2_Filters, kernel_size = (4, 4), strides = (1, 1), activation = 'relu')(pool)
    pool2 = MaxPooling2D(pool_size = (2, 2), strides = (2, 2))(conv2)
    flat = Flatten()(pool2)
    dense = Dense(dense_units, activation = 'relu')(flat)
    #model.add(Dropout(0.2))
    dense2 = Dense(dense2_units, activation = 'linear')(dense)
    output = Dense(num_classes, activation = 'softmax')(dense2)
    model = Model(inputs = visible, outputs = output)
    model.compile(loss = loss, optimizer = optimizer, metrics = ['accuracy'])
    print(model.summary())
    ##plot_model(model, to_file = 'cnn_graph.png')
    ##sys.exit()
    #
    return model
#
start_time = time.time()
#
# Задание затравки датчика случайных чисел обеспечит повторяемость результата
np.random.seed(348)
#
allParams = params()
batch_size = allParams[0]
epochs = allParams[2]
saveWeightsOnly = allParams[8]
#
# Загрузка данных и формирование обучающих и тестовых выборок
X_train, y_train, X_test, y_test, input_shape = load_data('G:/AM/НС/mnist', allParams)
#
# Создаем модель сверточной нейронной сети
model = createModel(input_shape, allParams)
#
# Обеспечим сохранение обученной сети
filepath = "weights.{epoch:02d}-{val_acc:.2f}.hdf5"
# Сохраняем и веса, и модель: save_weights_only = False
checkpoint = keras.callbacks.ModelCheckpoint(filepath, monitor = 'val_acc', verbose = 0,
                                             save_weights_only = saveWeightsOnly,
                                             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)
#
print('Число эпох: ', epochs)
print('batch_size = ', batch_size)
print('Время обучения: ', (time.time() - start_time))
#
# Проверка
start_time = time.time()
score = model.evaluate(X_test, y_test, verbose = 0)
#
# Вывод потерь и точности
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
# Время тестирования
print('Время тестирования: ', (time.time() - start_time))
#
# Вывод истории в файлы
print('История сохранена в файлы history_loss.txt и history_acc.txt')
with open('history_loss.txt', 'w') as output:
    for val in history.history['loss']: output.write(str(val) + '\n')
with open('history_acc.txt', 'w') as output:
    for val in history.history['acc']: output.write(str(val) + '\n')
#
# Вывод графиков
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()

Реализация сети для EMNIST-Letters

Реализации сетей для MNIST и EMNIST-Letters различаются числом классов, частично кодом ввода данных и числом элементов в предпоследнем Dense-слое (52 взамен 16).
При работе с EMNIST использованы те же средства ввода данных, что и для MNIST. Для этого выполнены приведенные в табл. 3 изменения имен файлов EMNIST-Letters (тип файлов – gz Archive):

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

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

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

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

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

В программе после чтения файла буквы отражаются относительно оси 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:img_rows, 0:img_rows, 0] # Первая буква тестового набора
let = let.reshape(32, 32)
let = np.flipud(let)
let = ndimage.rotate(let, -90)

или

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

Код, обеспечивающий работу с EMNIST-Letters:

from mnist import MNIST
import numpy as np
import time
import matplotlib.pyplot as plt
from scipy import ndimage
import keras
from keras.utils import plot_model
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import sys # Для sys.exit()
#
def params():
    #
    # Пользовательская функция потерь. На входе тензоры
    def myLoss(y_true, y_pred):
        dist = K.sqrt(K.sqrt(K.sum(K.square(y_pred - y_true))))
        err = dist
        return err # Вернет тензор с shape=(?,)
    #
    img_rows = img_cols = 28 # Размер входного образа
    #
    # Параметры модели
    batch_size = 256
    # Число классов (по числу букв)
    num_classes = 26
    # Число эпох обучения
    epochs = 12
    conv_filters = 20
    conv2_filters = 30
    dense_units = 600
    dense2_units = 30
    categorical = True # True; False для sparse_categorical_crossentropy
    #
    # Виды функций потерь:
    # mean_squared_error - mse;
    # mean_absolute_error - mae;
    # mean_absolute_percentage_error - mape;
    # mean_squared_logarithmic_error - msle;
    # squared_hinge - sh;
    # hinge;
    # categorical_hinge;
    # logcosh;
    # categorical_crossentropy;
    # sparse_categorical_crossentropy;
    # binary_crossentropy;
    # kullback_leibler_divergence;
    # cosine_proximity
    #
    userLoss = False # False True
    saveWeightsOnly = userLoss
    if userLoss:
        loss = myLoss
    else:
        loss = keras.losses.binary_crossentropy
    print(loss)
    #
    optimizer = keras.optimizers.Adam()
    #
    allParams = []
    allParams.append(batch_size)
    allParams.append(num_classes)
    allParams.append(epochs)
    allParams.append(loss)
    allParams.append(categorical)
    allParams.append(optimizer)
    allParams.append(img_rows)
    allParams.append(img_cols)
    allParams.append(saveWeightsOnly)
    allParams.append(conv_filters)
    allParams.append(conv2_filters)
    allParams.append(dense_units)
    allParams.append(dense2_units)
    return allParams
#
def loadBinData(fn, fn2, fn3, fn4):
    with open(fn, 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn2, 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn3, 'rb') as read_binary:
        data2 = np.fromfile(read_binary, dtype = np.uint8)
    with open(fn4, 'rb') as read_binary:
        labels2 = np.fromfile(read_binary, dtype = np.uint8)
    return data, labels, data2, labels2
#
def load_data(path, allParams):
    num_classes = allParams[1]
    categorical = allParams[4]
    img_rows = allParams[6]
    img_cols = allParams[7]
    loadFromBin = True
    if loadFromBin:
        imagesTrain, labelsTrain, imagesTest, labelsTest = loadBinData('imagesTrain.bin', 'labelsTrain.bin', 'imagesTest.bin', 'labelsTest.bin')
    else:
        emndata = MNIST(path)
        # Разрешаем чтение архивированных данных
        emndata.gz = True
        # Обучающая выборка (данные и метки)
        imagesTrain, labelsTrain = emndata.load_training()
        # Тестовая выборка (данные и метки)
        imagesTest, labelsTest = emndata.load_testing()
        saveData = False
        if saveData:
            fn = open('imagesTrain.bin', 'wb')
            fn2 = open('labelsTrain.bin', 'wb')
            fn3 = open('imagesTest.bin', 'wb')
            fn4 = open('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()
            sys.exit()
    # Преобразование списков в одномерные массивы
    # Каждый элемент массива - это вектор размера 28*28 с целочисленными данными в диапазоне [0, 255]
    X_train = np.asarray(imagesTrain)
    y_train = np.asarray(labelsTrain)
    X_test = np.asarray(imagesTest)
    y_test = np.asarray(labelsTest)
    y_train -= 1
    y_test -= 1
    if loadFromBin:
        X_train_shape_0 = 124800
        X_test_shape_0 = 20800
    else:
        X_train_shape_0 = X_train.shape[0]
        X_test_shape_0 = X_test.shape[0]
    #
    # Размер входного образа
    img_rows, img_cols = 28, 28
    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)
    ##X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1)
    input_shape = (img_rows, img_cols, 1)
    #
    show = False # False True
    if show:
        print(X_train.shape) # (12'4800, 28, 28, 1)
        print(y_train.shape) # (124'800,)
        print(X_test.shape) # (20'800, 28, 28, 1)
        print(y_test.shape) # (124'800,)
        # Выводим 9 изображений обучающего или тестового набора
        lets = []
        for i in range(26): lets.append(chr(65 + i)) # ['A', 'B', 'C', ..., 'Z']
        test = True # True or False
        for i in range(9):
            k = i
            plt.subplot(3, 3, i + 1)
            ind = y_test[k] if test else y_train[k]
            let = X_test[k][0:img_rows, 0:img_rows, 0] if test else X_train[k][0:img_rows, 0:img_rows, 0]
    ##        let = let.reshape(img_rows, img_cols).transpose()
    ## или
    ##        let = let.reshape(img_rows, img_cols)
    ##        let = np.flipud(let)
    ##        let = ndimage.rotate(let, -90)
            fig = plt.imshow(let, cmap = plt.get_cmap('gray'))
            plt.title(lets[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')
    #
    # Приведение к диапазону [0.0, 1.0]
    X_train /= 255
    X_test /= 255
    #
    # Преобразование метки - числа из диапазона [0, 25] в двоичный вектор размера 26
    # Так, метка 5 (соответствует букве F или f) будет преобразована в вектор [0. 0. 0. 0. 0. 1. 0. ... 0.]
    if categorical:
        y_train = keras.utils.to_categorical(y_train, num_classes)
        y_test = keras.utils.to_categorical(y_test, num_classes)
    #
    print('Форма X_train:', X_train.shape) # Напечатает: Форма X_train: (124800, 28, 28, 1)
    print(X_train.shape[0], 'обучающих образов') # Напечатает: 124800 обучающих образов
    print(X_test.shape[0], 'тестовых образов') # Напечатает: 20800 тестовых образов
    return X_train, y_train, X_test, y_test, input_shape
#
def createModel(input_shape, allParams):
    num_classes = allParams[1]
    loss = allParams[3]
    categorical = allParams[4]
    optimizer = allParams[5]
    conv_Filters = allParams[9]
    conv2_Filters = allParams[10]
    dense_units = allParams[11]
    dense2_units = allParams[12]
    #
    visible = Input(shape = input_shape)
    conv = Conv2D(conv_Filters, kernel_size = (4, 4), strides = (1, 1), padding = 'same', activation = 'relu')(visible)
    pool = MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same')(conv)
    conv2 = Conv2D(conv2_Filters, kernel_size = (4, 4), strides = (1, 1), activation = 'relu')(pool)
    pool2 = MaxPooling2D(pool_size = (2, 2), strides = (2, 2))(conv2)
    flat = Flatten()(pool2)
    dense = Dense(dense_units, activation = 'relu')(flat)
    #model.add(Dropout(0.2))
    dense2 = Dense(dense2_units, activation = 'linear')(dense)
    output = Dense(num_classes, activation = 'softmax')(dense2)
    model = Model(inputs = visible, outputs = output)
    model.compile(loss = loss, optimizer = optimizer, metrics = ['accuracy'])
    print(model.summary())
    ##plot_model(model, to_file = 'cnn_graph.png')
    ##sys.exit()
    #
    return model
#
start_time = time.time()
#
# Задание затравки датчика случайных чисел обеспечит повторяемость результата
np.random.seed(348)
#
allParams = params()
batch_size = allParams[0]
epochs = allParams[2]
saveWeightsOnly = allParams[8]
#
# Загрузка данных
X_train, y_train, X_test, y_test, input_shape = load_data('G:/AM/НС/emnist/', allParams)
#
# Создаем модель сверточной нейронной сети
model = createModel(input_shape, allParams)
#
# Обеспечим сохранение обученной сети
filepath = "weights.{epoch:02d}-{val_acc:.2f}.hdf5"
# Сохраняем и веса, и модель: save_weights_only = False
checkpoint = keras.callbacks.ModelCheckpoint(filepath, monitor = 'val_acc', verbose = 0,
                                             save_weights_only = saveWeightsOnly,
                                             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)
#
print('Число эпох: ', epochs)
print('batch_size = ', batch_size)
print('Время обучения: ', (time.time() - start_time))
#
# Проверка
print('Тестирование')
start_time = time.time()
score = model.evaluate(X_test, y_test, verbose = 0)
#
# Вывод потерь и точности
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
# Время тестирования
print('Время тестирования: ', (time.time() - start_time))
#
# Вывод истории в файлы
print('История сохранена в файлы history_loss.txt и history_acc.txt')
with open('history_loss.txt', 'w') as output:
    for val in history.history['loss']: output.write(str(val) + '\n')
with open('history_acc.txt', 'w') as output:
    for val in history.history['acc']: output.write(str(val) + '\n')
#
# Вывод графиков
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()

Реализации сети для CIFAR-10

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

import matplotlib.pyplot as plt
import numpy as np
import keras
import time
from keras.utils import plot_model
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K
import sys # Для sys.exit()
#
def unpickle(file):
    import pickle
    with open(file, 'rb') as fo:
        dict_file = pickle.load(fo, encoding = 'bytes')
    return dict_file
#
def myLoss(y_true, y_pred):
    dist = K.sqrt(K.sqrt(K.sum(K.square(y_pred - y_true))))
    err = dist
    return err # Вернет тензор с shape=(?,)
#
np.random.seed(348)
# Загрузка обучающих данных
len_train = 50000
X_train = np.zeros((len_train, 3072), dtype = 'uint8')
y_train = np.zeros(len_train, dtype = 'uint8')
fn = 'cifar-10-batches-py/data_batch_'
m = -1
for i in range(5):
    dict_i = unpickle(fn + 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, 32, 32).transpose(0,2,3,1) # .astype('uint8')
#
# Загрузка тестовых данных
len_test = 10000
dict_i = unpickle('cifar-10-batches-py/data_batch_' + str(i + 1))
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)
##print(X_test.shape) # Напечатает: (10000, 32, 32, 3), поскольку выполнили transpose(0,2,3,1)
#
show = False # False True
if show:
    showTest = True
    len_show = len_test if showTest else len_train
    # Вывод случайных изображений
    fig, axes1 = plt.subplots(5, 10, figsize = (8, 4))
    for j in range(5):
        for k in range(10):
            i = np.random.choice(range(len_show))
            axes1[j][k].set_axis_off()
            if showTest:
                axes1[j][k].imshow(X_test[i])
            else:
                axes1[j][k].imshow(X_train[i])
    plt.show()
    sys.exit()
#
# Преобразование из диапазона 0-255 в диапазон 0.0-1.0
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train = X_train / 255.0
X_test = X_test / 255.0
#
categorical = True
num_classes = 10
batch_size = 256
epochs = 3
img_rows, img_cols = 32, 32
userLoss = False # False True
saveWeightsOnly = userLoss
if userLoss:
    loss = myLoss
else:
    loss = keras.losses.kullback_leibler_divergence
optimizer = keras.optimizers.Adam()
if categorical:
    y_train = keras.utils.to_categorical(y_train, num_classes)
    y_test = keras.utils.to_categorical(y_test, num_classes)
input_shape = (img_rows, img_cols, 3)
start_time = time.time()
#
# Создаем модель сверточной нейронной сети
visible = Input(shape = input_shape)
hidden1 = Conv2D(32, kernel_size = (3, 3), padding = 'same', activation = 'relu')(visible)
hidden2 = MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same')(hidden1)
hidden3 = Conv2D(64, kernel_size = (3, 3), padding = 'same', activation = 'relu')(hidden2)
hidden4 = MaxPooling2D(pool_size = (2, 2), strides = (2, 2), padding = 'same')(hidden3)
hidden5 = Flatten()(hidden4)
hidden6 = Dense(1024, activation = 'relu')(hidden5)
hidden7 = Dense(16, activation = 'linear')(hidden6)
output = Dense(num_classes, activation = 'softmax')(hidden7)
model = Model(inputs = visible, outputs = output)
model.compile(loss = loss, optimizer = optimizer, metrics = ['accuracy'])
print(model.summary())
filepath = "weights.{epoch:02d}-{val_acc:.2f}.hdf5"
checkpoint = keras.callbacks.ModelCheckpoint(filepath, monitor = 'val_loss', verbose = 0,
                                             save_weights_only = saveWeightsOnly,
                                             save_best_only = True, mode = 'min', period = 1)
callbacks_list = [checkpoint]
# Получаем историю после завершения эпохи
history = model.fit(X_train, y_train, batch_size = batch_size, epochs = epochs,
                    verbose = 1, validation_data = (X_test, y_test), callbacks = callbacks_list)
print('Число эпох: ', epochs)
print('batch_size = ', batch_size)
print('Время обучения: ', (time.time() - start_time))
#
# Проверка
print('Тестирование')
start_time = time.time()
score = model.evaluate(X_test, y_test, verbose = 0)
print('Потери при тестировании: ', score[0])
print('Точность при тестировании:', score[1])
print('Время тестирования: ', (time.time() - start_time))
#
# Вывод истории в файлы
print('История сохранена в файлы history_loss.txt и history_acc.txt')
with open('history_loss.txt', 'w') as output:
    for val in history.history['loss']: output.write(str(val) + '\n')
with open('history_acc.txt', 'w') as output:
    for val in history.history['acc']: output.write(str(val) + '\n')
#
# Вывод графиков
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()

Результаты

Результаты, показанные на тестовых данных, приведены в табл. 4

Таблица 4. Результаты тестирования СНС

Набор данныхТочность, %Эпоха
MNIST99.3913
EMNIST93.855
CIFAR-1099.7218

Источники

1. The MNIST database of handwritten digits. [Электронный ресурс] URL: http://yann.lecun.com/exdb/mnist/ (дата обращения: 31.12.2018).
2. The EMNIST dataset. [Электронный ресурс] URL: https://www.nist.gov/itl/iad/image-group/emnist-dataset (дата обращения: 31.12.2018).
3. The CIFAR-10 dataset. [Электронный ресурс] URL: http://www.cs.toronto.edu/~kriz/cifar.html (дата обращения: 31.12.2018).

Список работ

Рейтинг@Mail.ru