Как извлечь сверточную нейронную сеть из объекта модели Keras в объект Networkx DiGraph, сохраняя веса в качестве атрибута ребра? - PullRequest
4 голосов
/ 03 апреля 2020

Мне интересно использовать пакет Networkx Python для выполнения анализа сети на сверточных нейронных сетях . Для этого я хочу извлечь информацию edge и weight из объектов модели Keras и поместить их в объект Networkx Digraph , где он можно (1) записать в файл graphml и (2) использовать инструменты анализа графа, доступные в Networkx.

Прежде чем переходить к дальнейшему, позвольте мне уточнить и как рассмотреть пулы . Объединение (примеры: макс или среднее ) означает, что записи в свертке окне будут агрегированными создание неоднозначности в отношении того, какая запись будет использоваться в графе, который я хочу создать. Чтобы решить эту проблему, я бы хотел, чтобы каждый возможный выбор был включен в график, поскольку я могу учесть это позже при необходимости.

Для примера, давайте рассмотрим это с VGG16 . Keras позволяет довольно просто получить доступ к весу с, зацикливаясь на слоях .

from keras.applications.vgg16 import VGG16

model = VGG16()

for layer_index, layer in enumerate(model.layers):
    GW = layer.get_weights()
    if layer_index == 0:
        print(layer_index, layer.get_config()['name'], layer.get_config()['batch_input_shape'])
    elif GW:
        W, B =  GW
        print(layer_index, layer.get_config()['name'], W.shape, B.shape)
    else:
        print(layer_index, layer.get_config()['name'])

, которые напечатают следующее:

0 input_1 (None, 224, 224, 3)
1 block1_conv1 (3, 3, 3, 64) (64,)
2 block1_conv2 (3, 3, 64, 64) (64,)
3 block1_pool
4 block2_conv1 (3, 3, 64, 128) (128,)
5 block2_conv2 (3, 3, 128, 128) (128,)
6 block2_pool
7 block3_conv1 (3, 3, 128, 256) (256,)
8 block3_conv2 (3, 3, 256, 256) (256,)
9 block3_conv3 (3, 3, 256, 256) (256,)
10 block3_pool
11 block4_conv1 (3, 3, 256, 512) (512,)
12 block4_conv2 (3, 3, 512, 512) (512,)
13 block4_conv3 (3, 3, 512, 512) (512,)
14 block4_pool
15 block5_conv1 (3, 3, 512, 512) (512,)
16 block5_conv2 (3, 3, 512, 512) (512,)
17 block5_conv3 (3, 3, 512, 512) (512,)
18 block5_pool
19 flatten
20 fc1 (25088, 4096) (4096,)
21 fc2 (4096, 4096) (4096,)
22 predictions (4096, 1000) (1000,)

Для сверточных слоев я читал, что кортежи будут представлять (filter_x, filter_y, filter_z, num_filters), где filter_x, filter_y, filter_z задают форму фильтра, а num_filters - количество фильтров. , Существует один термин смещения для каждого фильтра, поэтому последний кортеж в этих строках также будет равен количеству фильтров.

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

Как только я узнаю, как перевести границы модели Keras на oop, я смогу легко кодировать конструкцию объекта Networkx. Код для этого может примерно напоминать что-то вроде этого, где keras_edges - это итерация, которая содержит кортежи, отформатированные как (in_node, out_node, edge_weight).

import networkx as nx

g = nx.DiGraph()

g.add_weighted_edges_from(keras_edges)

nx.write_graphml(g, 'vgg16.graphml') 

Так что для уточнения c, как это сделать I oop по всем краям таким образом, чтобы учитывать форму слоев и объединение так, как я описал выше?

1 Ответ

0 голосов
/ 06 апреля 2020

Поскольку у Keras нет элемента ребра, а узел Keras кажется чем-то совершенно другим (когда он используется, узел Keras - это целый слой, это слой, представленный на графике модели)

Итак, если вы используете наименьшее возможное изображение (равное размеру ядра) и создаете узлы вручную (извините, я не знаю, как это работает в сети x):

Для свертки:

  • Имеет i входные каналы (каналы на входящем изображении)
  • Имеет o выходные каналы (выбранное количество фильтров в керас)
  • Имеет kernel_size = (x, y)

Вы уже знаете веса, которые имеют форму (x, y, i, o).

Вы бы хотели что-то вроде:

#assuming a node here is one pixel from one channel only:

#kernel sizes x and y
kSizeX = weights.shape[0]
kSizeY = weights.shape[1]

#in and out channels
inChannels = weights.shape[2]
outChannels = weights.shape[3]

#slide steps x
stepsX = image.shape[0] - kSizeX + 1
stepsY = image.shape[1] - kSizeY + 1


#stores the final results
all_filter_results = []

for ko in range(outChannels): #for each output filter

    one_image_results = np.zeros((stepsX, stepsY))

    #for each position of the sliding window 
    #if you used the smallest size image, start here
    for pos_x in range(stepsX):      
        for pos_y in range(stepsY):

            #storing the results of a single step of a filter here:
            one_slide_nodes = []

            #for each weight in the filter
            for kx in range(kSizeX):
                for ky in range(kSizeY):
                    for ki in range(inChannels):

                        #the input node is a pixel in a single channel
                        in_node = image[pos_x + kx, pos_y + ky, ki]

                        #one multiplication, single weight x single pixel
                        one_slide_nodes.append(weights[kx, ky, ki, ko] * in_node)

                        #so, here, you have in_node and weights

            #the results of each step in the slide is the sum of one_slide_nodes:
            slide_result = sum(one_slide_nodes)
            one_image_results[pos_x, pos_y] = slide_result   
    all_filter_results.append(one_image_results)
...