Список работ

Сборка двух сетей

Содержание

Введение

Рассматривается пример создания сборки (ансамбля) двух нейронных сетей (НС).
Сборка решает ранее рассмотренную задачу классификации графиков элементарных функций и простых геометрических фигур.

Структура сборки показана на рис. 1.

Структура сборки

Рис. 1. Структура сборки двух нейронных сетей

Структура каждой НС воспроизводится в результате выполнения следующего кода:

from keras.utils import plot_model
plot_model(model, to_file = 'nn_graph.png')

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

print(model.summary())

Описание структуры первой сети:

Layer (type)                 Output Shape              Param #
==================================================
input_1 (InputLayer)         (None, 64, 64, 1)                0
________________________________________________________
conv2d_1 (Conv2D)          (None, 64, 64, 2)              34
________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 2)         0
________________________________________________________
flatten_1 (Flatten)              (None, 128)                        0
________________________________________________________
dense_1 (Dense)                 (None, 16)                    2064
________________________________________________________
dense_2 (Dense)                 (None, 6)                        102
==================================================
Total params: 2,200
Trainable params: 2,200
Non-trainable params: 0

Описание структуры второй сети:

Layer (type)                 Output Shape              Param #
=================================================
input_2 (InputLayer)        (None, 6)                     0
_______________________________________________________
dense_3 (Dense)              (None, 16)                112
_______________________________________________________
dense_4 (Dense)              (None, 48)                 816
_______________________________________________________
dense_5 (Dense)              (None, 12)                 588
_______________________________________________________
dense_6 (Dense)              (None, 6)                     78
=================================================
Total params: 1,594
Trainable params: 1,594
Non-trainable params: 0

Классифицируемые изображения записаны в двоичные файлы dataTrain.bin, labelsTrain.bin, dataTest.bin, labelsTest.bin, содержащие соответственно обучающие и тестовые данные.
Файлы data* содержат сведения об изображениях, а файлы labels* – соответствующие им метки (табл. 1).

Таблица 1. Классы изображений и их метки

КлассМетка
Часть параболы    0
Парабола    1
Часть экспоненты    2
Экспонента    3
Прямоугольник без одной стороны    4
Прямоугольник    5

Фактически метка – это номер класса.
Все изображения выполнены в оттенках серого цвета на площадке размером 64*64 пикселя.
Каждый пиксель содержит число в диапазоне [0, 255].
Всего в файле с обучающими данными находятся сведения о 600-х изображениях, а в файле с тестовыми данными – о 150-и.

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

# Выход первой сети
print('Подготовка входа второй сети (обучающие и тестовые данные)')
train_pred = model.predict(train_data, batch_size = 1)
test_pred = model.predict(test_data, batch_size = 1)

Тип выход покажет

print(type(test_pred)) # <class 'numpy.ndarray'>

Несколько строк выхода первой сети (печатается test_pred):

[[ 4.16896820e-01 1.31221205e-01 5.70299029e-01 4.62679490e-02 8.79633874e-02 1.51358783e-01]
 [ 5.18408954e-01 7.38345161e-02 6.20221198e-01 1.20329976e-01 1.03515081e-01 1.17906049e-01] ...]

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

Реализация

import numpy as np
import sys # Для sys.exit()
import matplotlib.pyplot as plt
import time
import keras
from keras.utils import plot_model
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers import Conv1D, Conv2D, MaxPooling1D, MaxPooling2D
from keras import backend as K
from keras import optimizers
#
# Пользовательская функция потерь. На входе тензоры
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 = (?, )
#
def loadData(fn, fn2):
    with open(fn, 'rb') as read_binary:
        data = np.fromfile(read_binary, dtype = np.int16)
    with open(fn2, 'rb') as read_binary:
        labels = np.fromfile(read_binary, dtype = np.int16)
    return data, labels
#
np.random.seed(348)
fn_train, fn_train_labels = 'dataTrain.bin', 'labelsTrain.bin'
fn_test, fn_test_labels = 'dataTest.bin', 'labelsTest.bin'
num_classes = 6 # Число классов
w, h = 64, 64 # Ширина и высота окна вывода рисунка
categorical = True
#
print('Число классов: ' + str(num_classes))
train_data, train_labels = loadData(fn_train, fn_train_labels)
test_data, test_labels = loadData(fn_test, fn_test_labels)
n_train = int(train_data.size / (w * h)) # Число рисунков для обучения
n_test = int(test_data.size / (w * h)) # Число тестовых рисунков
#
train_data, test_data = train_data.reshape(n_train, w, h, 1), test_data.reshape(n_test, w, h, 1)
train_data, test_data = train_data.astype('float32'), test_data.astype('float32')
train_data /= 255
test_data /= 255
test_labels_0 = test_labels # Для сравнения с результатами прогноза
if categorical:
    train_labels = keras.utils.to_categorical(train_labels, num_classes)
    test_labels = keras.utils.to_categorical(test_labels, num_classes)
#
print(train_data.shape[0], 'обучающих образов')
print(test_data.shape[0], 'тестовых образов')
#
visible = Input(shape = (w, h, 1))
hidden1 = Conv2D(2, kernel_size = (4, 4), strides = (1, 1), padding = 'same', activation = 'relu')(visible)
hidden2 = MaxPooling2D(pool_size = (8, 8), strides = (8, 8), padding = 'same')(hidden1)
hidden3 = Flatten()(hidden2)
hidden4 = Dense(16, activation = 'relu')(hidden3)
output = Dense(num_classes, activation = 'linear')(hidden4)
model = Model(inputs = visible, outputs = output)
model.compile(loss = myLoss, optimizer = 'adam', metrics = ['accuracy'])
##print(model.summary())
##plot_model(model, to_file = 'nn_graph.png')
#
cnn = False
if cnn:
    visible2 = Input(shape = (num_classes, 1))
    hidden2_1 = Conv1D(1, kernel_size = 4, activation = 'relu')(visible2)
    hidden2_2 = MaxPooling1D(pool_size = 2)(hidden2_1)
    hidden2_3 = Flatten()(hidden2_2)
    hidden2_4 = Dense(16, activation = 'relu')(hidden2_3)
    output2 = Dense(num_classes, activation = 'softmax')(hidden2_4) # softmax, sigmoid
else:
    visible2 = Input(shape = (num_classes, ))
    hidden2_1 = Dense(16, activation = 'linear')(visible2)
    hidden2_2 = Dense(48, activation = 'linear')(hidden2_1)
    hidden2_3 = Dense(12, activation = 'linear')(hidden2_2)
    output2 = Dense(num_classes, activation = 'softmax')(hidden2_3)
model2 = Model(inputs = visible2, outputs = output2)
model2.compile(loss = myLoss, optimizer = 'adam', metrics = ['accuracy'])
##print(model2.summary())
##plot_model(model2, to_file = 'mlp_graph.png')
#
# Обучение
print('\nОбучение 1')
start_time = time.time()
model.fit(train_data, train_labels, batch_size = 10, epochs = 5, verbose = 0)
print('Время обучения 1: ', (time.time() - start_time))
#
print('Тестирование 1')
score = model.evaluate(test_data, test_labels, verbose = 0)
print('Потери при тестировании 1: ', score[0])
print('Точность при тестировании 1:', score[1])
#
# Выход первой сети
print('\nПодготовка входа второй сети (обучающие и тестовые данные)')
train_pred = model.predict(train_data, batch_size = 10)
test_pred = model.predict(test_data, batch_size = 10)
if cnn:
    dim0, dim1 = train_pred.shape[0], train_pred.shape[1]
    train_pred = train_pred.reshape(dim0, dim1, 1)
    dim0, dim1 = test_pred.shape[0], test_pred.shape[1]
    test_pred = test_pred.reshape(dim0, dim1, 1)
#
print('\nОбучение 2')
start_time = time.time()
model2.fit(train_pred, train_labels, batch_size = 10, epochs = 5, verbose = 0)
print('Время обучения 2: ', (time.time() - start_time))
#
print('Тестирование 2')
score2 = model2.evaluate(test_pred, test_labels, verbose = 0)
print('Потери при тестировании 2: ', score2[0])
print('Точность при тестировании 2:', score2[1])
#
print("Прогноз")
test_pred2 = model2.predict(test_pred) # , batch_size = 10
# Формируем массив предсказанных классов
classes = np.zeros(n_test, dtype = np.int)
k = - 1
for pred in test_pred2:
    k += 1
    classes[k] = np.argmax(pred)
# np.sum(classes == test_labels) вернет сумму случаев, когда classes[i] = test_labels[i]
acc = np.sum(classes == test_labels_0) / n_test * 100
print(classes)
print("На самом деле:")
print(test_labels_0)
print("Точность прогнозирования: " + str(acc) + '%')
if acc < 100:
    show = True
    for i in range(n_test):
        if classes[i] != test_labels_0[i]:
            print('i = ' + str(i) + '. Прогноз: ' + str(classes[i]) + '. На самом деле: ' + str(test_labels_0[i]))
            if show:
                show = False
                plt.subplot()
                plt.imshow(test_data[i].reshape(w, h), cmap = plt.get_cmap('gray'))
                plt.show()

Выводятся следующие сведения:
Число классов: 6
600 обучающих образов
150 тестовых образов

Обучение 1
Время обучения 1: 12.615854740142822
Тестирование 1
Потери при тестировании 1: 0.9830639735857646
Точность при тестировании 1: 0.9866666666666667

Подготовка входа второй сети (обучающие и тестовые данные)

Обучение 2
Время обучения 2: 1.8649725914001465
Тестирование 2
Потери при тестировании 2: 0.4106965178251267
Точность при тестировании 2: 0.9933333333333333

Заключение

В рассматриваемой задаче две сети работают хуже, чем одна: одни образ оказался нераспознанным. Он показан на рис. 2.

Нераспознанный образ

Рис. 2. Нераспознанный образ

Не решает проблемы и использование дополнительного LeakyReLU-слоя [1]: по-прежнему один образ остается нераспознанным.
Добавление LeakyReLU становится возможным после его импортирования:

from keras.layers import LeakyReLU

Тогда модель второй сети можно записать следующим образом:

    visible2 = Input(shape = (num_classes, ))
    hidden2_1 = Dense(16, activation = 'linear')(visible2)
    hidden2_2 = Dense(48, activation = 'linear')(hidden2_1)
    hidden2_3 = Dense(12)(hidden2_2)
    hidden2_4 = LeakyReLU(alpha = 0.4) (hidden2_3)
    output2 = Dense(num_classes, activation = 'softmax')(hidden2_4)

Вторая сеть сохраняет 100% классификацию, если первая сеть распознала все образы (это получается при усложнении структуры первой сети).
Если же первая сеть не справляется с классификацией, то вторая сеть, улучшая результат, так же не достигает 100% результата.
В то же время одновременное применение двух и более сетей для решения задачи предоставляет следующие дополнительные возможности. Вот некоторые из них:

Литература

1. Keras. Advanced activations. [Электронный ресурс] URL: https://keras.io/layers/advanced-activations/ (дата обращения: 29.06.2018).

Список работ

Рейтинг@Mail.ru