Мозг изнутри (Визуализация прохождения паттерна через модель искусственной нейронной сети)

Введение


Статья предназначена для тех, кто когда-либо интересовался вопросом о том, что же происходит внутри искусственной нейронной сети (artificial neural network)ИНС. Сейчас разработать собственную ИНС может практически каждый, используя уже готовые библиотеки, имеющиеся в большинстве языков программирования. В рассматриваемой статье, я постараюсь показать, как именно выглядит объект (Паттерн), проходящий через слои ИНС, разработанной и скомпилированной при помощи библиотеки глубокого обучения Tensorflow с надстройкой Keras.

Используемое ПО


Необходимы следующие компоненты (версии я указал для своего случая):

  • tensorflow 1.10.0
  • keras 2.2.4
  • matplotlib 2.2.0
  • modul-os
  • numpy1.14.3

Также есть возможность нарисовать архитектуру сети, но для этого необходимо установить средства визуализации, в моем случае было использовано keras, и в методе

PLOT_PATTERN_PROCCESS(...)

установить

PLOT_MODEL=True


def PLOT_PATTERN_PROCCESS(model, pattern, FOLDER_TO_SAVE, grid_size=(3, 3), limit_size_layer=(15, 15), PLOT_MODEL=True):

image

Основная идея


Необходимо выбрать один паттерн (прохождение, которого мы будем наблюдать), после его выбора сеть разбивается на слои tensor'ов. В цикле от второго до последнего слоя, создается новая сеть, где выход, — это номер слоя по циклу, и пропуская паттерн, на выходе сети получается результат в виде n-мерного массива.

Реализация


Подключение библиотек

from keras.models import *
from keras.layers import *
import matplotlib.pyplot as plt
import os
import numpy as np

Используемые методы:

  • def PLOT_PATTERN_PROCCESS(model, pattern, FOLDER_TO_SAVE, grid_size=(3, 3), limit_size_layer=(15, 15), PLOT_MODEL=True):
    def PLOT_PATTERN_PROCCESS(model, pattern, FOLDER_TO_SAVE, grid_size=(3, 3), limit_size_layer=(15, 15), PLOT_MODEL=True):
        """
        :param model: Модель нейроархитектуры keras
        :type model: Sequential
        :param pattern: Входной паттерн, массив данных соответвующий размеру входных слоев
        :type pattern: np.array
        :param FOLDER_TO_SAVE: Папка в которую будет сохраняться результат
        :type FOLDER_TO_SAVE: str
        :param grid_size: Размер отображаемой сетки слоев
        :type grid_size: tuple
        :param limit_size_layer: Минимальный размер для отображения слоя
        :type limit_size_layer: tuple
        :param PLOT_MODEL: Выполнить построение модели
        :type PLOT_MODEL: PLOT_MODEL
        """
        SAVE_AR_LIST = []
        for num_layer in range(1, len(model.layers)):
            LO = model.layers[num_layer].output
            _model = Model(inputs=model.input, outputs=LO)
            if (
                    len(_model.output_shape) == 3 and
                    _model.output_shape[1] > limit_size_layer[0] and
                    _model.output_shape[2] > limit_size_layer[1]
            ):
                _output = _model.predict(pattern)[0]
                SAVE_AR_LIST.append(
                    [
                        num_layer,
                        model.layers[num_layer].name,
                        _output.tolist()
                    ]
                )
        ###
        PIC_NUM = 0
        while len(SAVE_AR_LIST) > 0:
            fig, axs = plt.subplots(nrows=grid_size[0], ncols=grid_size[1], figsize=(10, 10), tight_layout=True)
            xmin, xmax = plt.xlim()
            ymin, ymax = plt.ylim()
            for ax in axs.flat:
                [num_layer, layer_name, ar] = SAVE_AR_LIST.pop(0)
                ax.imshow(np.array(ar), cmap='viridis', extent=(xmin, xmax, ymin, ymax))
                ax.set_title(layer_name + " " + str(np.array(ar).shape))
                if len(SAVE_AR_LIST) == 0:
                    break
            # plt.show()
            plt.savefig(os.path.join(FOLDER_TO_SAVE, str(PIC_NUM) + '.png'), fmt='png')
            plt.close(fig)
            PIC_NUM += 1
        ###
        if PLOT_MODEL:
            from keras.utils.vis_utils import plot_model
            plot_model(
                model=model,
                to_file=os.path.join(FOLDER_TO_SAVE, model.name + " neural network architecture.png"),
                show_shapes=True,
                show_layer_names=True
            )
        ###
    

  • def build_model(IN_SHAPE=50,CLASSES=5) -> Sequential:
    def build_model(IN_SHAPE=50,CLASSES=5) -> Sequential:
    
        inputs_LAYER0 = Input(shape=(IN_SHAPE,IN_SHAPE))
        Dense_2_2 = Dense(75, activation='relu')(inputs_LAYER0)
        Dense_2_3 = Dense(50, activation='relu', name="my_dense")(Dense_2_2)
        Dense_2_4 = Dense(25, activation='relu')(Dense_2_3)
        Dense_2_5 = Dense(10, activation='relu')(Dense_2_4)
        flat_f_0 = Flatten()(Dense_2_5)
        final_layer= Dense(CLASSES, activation='softmax')(flat_f_0)
        #
        model = Model(input=inputs_LAYER0, output=final_layer, name="simple model")
        model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
        model.summary()
        return model


Код программы

model_ = build_model()
pattern = np.random.sample((1,50,50))
os.makedirs("PLOT_PATTERN_PROCCESS")
PLOT_PATTERN_PROCCESS(
    model = model_,
    pattern = pattern,
    FOLDER_TO_SAVE = "PLOT_PATTERN_PROCCESS",
    PLOT_MODEL=False,
    grid_size=(2, 2)
)

Описание работы программы


Метод

build_model()

возвращвет модель ИНС в формате Sequential, предназначенную для классификации чего-либо в 5 классов.

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 50, 50)            0         
_________________________________________________________________
dense_1 (Dense)              (None, 50, 75)            3825      
_________________________________________________________________
my_dense (Dense)             (None, 50, 50)            3800      
_________________________________________________________________
dense_2 (Dense)              (None, 50, 25)            1275      
_________________________________________________________________
dense_3 (Dense)              (None, 50, 10)            260       
_________________________________________________________________
flatten_1 (Flatten)          (None, 500)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 5)                 2505      
=================================================================
Total params: 11,665
Trainable params: 11,665
Non-trainable params: 0
_________________________________________________________________


Как видно из архитектуры, паттерн — это массив размером 50х50. Переменная

pattern

и есть наблюдаемый объект.
Далее создается директория

os.makedirs("PLOT_PATTERN_PROCCESS")
,
куда будет сохраняться весь результат.

Описание метода PLOT_PATTERN_PROCCESS


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

_output = _model.predict(pattern)[0]

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

limit_size_layer

Поочередно перебирая слои модели ИНС, переменная

SAVE_AR_LIST
постепенно заполняется данными:

  1. Номер слоя

    num_layer
  2. Имя слоя

    model.layers[num_layer].name
  3. Выходной двумерный массив

     _output.tolist()

Постепенно исключая по одному результату из

SAVE_AR_LIST
,
и помещая его в ячейку холста

ax.imshow(np.array(ar), cmap='viridis', extent=(xmin, xmax, ymin, ymax))
.
В результате создается файл (0.png)

image

Рекомендации


  • Задать имя слоя можно следующим образом:

    Dense_2_3 = Dense(50, activation='relu', name="my_dense")(Dense_2_2)

    Это очень удобно при оценке и сравнении с нейроархитектурой.
  • С помощью такого подхода интересно смотреть, как изменяется паттерн проходя сеть при обучении от эпохи к эпохе
  • Не стоит устанавливать сетку

    grid_size

    большого размера т.к. размер отображаемых изображений будет маленьким и неинформативным
  • Если наблюдаете прохождение в динамике (при обучении или прохождении пачки паттернов), речь уже идёт об информации больших размеров. Для уменьшения занимаемого приложением ОЗУ, лучше сохранять массивы в файлы на ПК, например, в формате JSON, а после обработки всех паттернов, перебирать файлы поочередно и превращать их в изображения

Успехов!
  • +14
  • 2,7k
  • 1
Поделиться публикацией

Комментарии 1

    0
    Картинки бы перезалить на habrastorage…

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое