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

У меня есть многослойный персептрон, который я сделал с Keras с использованием серверной части Tensorflow, и я хотел бы перенести структуру графа и соответствующие веса из объекта модели Keras в объект Networkx DiGraph, чтобы я мог дополнительно проанализировать его структуру графа , Я попытался реализовать это в приведенном ниже коде, последний слой, кажется, неправильно получается в получающейся структуре графа.

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

Я реализовал это в двух скрипты. Первый сценарий создает и обучает модель, которая в качестве игрушечного примера учится приближать логический оператор EXOR. Я включил оба сценария, чтобы при наличии установленных зависимостей можно было воспроизвести как модель в ее топологии, так и в структуре данных. Хотя точные числовые веса будут разными из-за того, что я не использую начальное число для управления начальными значениями параметров, это не должно влиять на отладку этого кода, поскольку числовые значения весовых коэффициентов не учитываются при построении сети. Второй сценарий берет модель, сгенерированную в первом сценарии, и преобразует ее в объект Networkx.

Скрипт 1

Сначала я импортировал библиотеки, которые мне могут понадобиться

import numpy as np
import matplotlib.pyplot as plt
import keras
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD

Затем я создал синтетический набор данных c, который охватывает все возможные входы / выходы логического оператора EXOR.

X = np.array([[0,0],[0,1],[1,0],[1,1]], "float32")
y = np.array([[0],[1],[1],[0]], "float32")

Затем я определяю последовательный и полностью подключенный многослойный персептрон.

model = Sequential()
model.add(Dense(units=2, input_dim=2, activation = 'tanh'))
model.add(Dense(units=2, activation = 'tanh'))
model.add(Dense(units = 1, activation = 'sigmoid'))

model.compile(loss='binary_crossentropy', optimizer=SGD())
model.summary()

Затем я обучил модель данным.

history = model.fit(X, y, epochs=10000, batch_size=1, verbose=1)

Затем я сохранил модель для загрузки в следующем скрипте.

model.save('EXOR.h5')

Скрипт 2

Сначала я загружаю зависимости скрипта.

from keras.models import load_model
import networkx as nx
import numpy as np

Затем загружаю модель, сгенерированную из предыдущего скрипта.

model = load_model('EXOR.h5')

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

node_counter = -1
node_dict = {}
g = nx.DiGraph()

Затем I l oop над слоями, и для каждого слоя я получаю и распаковать его вес. I l oop над столбцами матрицы весов, а для каждого столбца I l oop над записями строк. Используя node_counter, я пытаюсь создать уникальную карту всех узлов и где они соответствуют в сети, как I l oop весов. Я также oop за уклоны подобным образом. Зацикливаясь на весах и смещениях, я занимаю объект Networkx, включающий атрибут веса ребра и атрибут слоя узла.

for layer_index, layer in enumerate(model.layers):

    W, B = layer.get_weights()

    for i in range(W.shape[1]):
        for j in range(W.shape[0]):
            if (layer_index, i) not in node_dict.keys():
                node_counter += 1
                node_dict[(layer_index, i)] = node_counter
                g.add_node(node_counter, layer=layer_index)
            if (layer_index+1, j) not in node_dict.keys():
                node_counter += 1
                node_dict[(layer_index+1, j)] = node_counter
                g.add_node(node_counter, layer=layer_index+1)
            print((layer_index, i), (layer_index+1, j), W[j,i])
            g.add_edge(node_dict[(layer_index, i)], node_dict[(layer_index+1, j)], weight=W[j,i])

    for b in range(B.shape[0]):
        if (layer_index, W.shape[1]) not in node_dict.keys():
                node_counter += 1
                node_dict[(layer_index, W.shape[1])] = node_counter
                g.add_node(node_counter, layer=layer_index)
        if (layer_index+1, b) not in node_dict.keys():
                node_counter += 1
                node_dict[(layer_index+1, b)] = node_counter
                g.add_node(node_counter, layer=layer_index+1)
        print((layer_index, W.shape[1]), (layer_index+1, b), B[b])
        g.add_edge(node_dict[(layer_index, W.shape[1])], node_dict[(layer_index+1, b)], weight=B[b])

В то время как я нарушаю соглашение о размещении всех импортов в начале файлов кода, я быстро закрепил график сети, где я могу видеть пути, окрашенные +/- (красный / синий). Именно здесь я понял, что веса и смещения от второго до последнего слоя были размещены неправильно. Не обязательно включать это, но если вы будете следовать этому, это даст способ визуализировать проблему более четко, чем я мог бы заявить словами.

import matplotlib.pyplot as plt
pos={v: np.array(k) for k, v in node_dict.items()}
nx.draw(g, pos, edge_color=['r' if weight[2] > 0 else 'b' for weight in g.edges(data='weight')], node_color=[(0.5, 0.5, 0.5)])
nx.draw_networkx_labels(g, pos)
nx.draw_networkx_edge_labels(g,pos,nx.get_edge_attributes(g, 'weight'))
plt.show()

Я подозреваю, что неожиданное поведение исходит из того факта, что форма массивов, которые содержат веса / отклонения, отличаются в последнем слое по сравнению с другими. Что я ищу в ответе на этот пост, так это изменение большого l oop во втором скрипте, которое позволит мне l oop поверх слоев различной формы, чтобы правильно извлечь структуру графика и веса объект keras в объект Networkx.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...