Графовые нейронные сети (Graph Neural Net, GNN) — набирающие популярность архитектуры в Machine Learning и Deep Learning. Одна из причин их популярности состоит в том, что данные и связи между ними могут быть представлены в виде графа. В этой статье мы научимся строить графовые нейронные сети с нуля.
Графовые нейронные сети что это
Графовые нейронные сети применяются для графовых данных. Например, в задаче классификации GNN должна классифицировать узлы графа: сами узлы должны представлять собой метку некоторого класса. Поэтому данные, которые представляют собой графовую структуру (например, связи в социальных сетях), могут быть использованы для обучения GNN.
Рассмотрим пример графовых нейронных сетей с использованием TensorFlow.
Пример графовых данных
Мы будет использовать специальный датасет Cora. Этот датасет состоит из 2708 статей о Machine Learning, каждая из которых разделена на одну из 7 тематик и имеет ссылки (цитирования) на другие статьи. Статья имеет бинарный 1433-мерный вектор, где 0/1 представляет имеет ли он слово из заданного словаря.
В первую очередь скачаем датасет:
import os from tensorflow import keras url = "https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz" zip_file = keras.utils.get_file( fname="cora.tgz", origin=url, extract=True ) data_dir = os.path.join(os.path.dirname(zip_file), "cora")
Датасет снабжен двумя файлами:
- `cora.cities`, который включает цитирования; - `cora.content`, который включает сами статьи.
Преобразуем данные с цитированием в DataFrame:
import pandas as pd citations_data = pd.read_csv( os.path.join(data_dir, "cora.cites"), sep="\t", header=None, names=["target", "source"], )
Он имеет два атрибута: source
(кто цитирует) и target
(кого цитирует).
Теперь представим содержимое файла core.content
в виде мешка слов, о котором говорили тут:
column_names = ["paper_id"] + [f"term_{idx}" for idx in range(1433)] + ["subject"] papers_data = pd.read_csv( os.path.join(data_dir, "cora.content"), sep="\t", header=None, names=column_names, )
Рассмотрим, что получилось:
print("Papers shape:", papers_data.shape) papers_data.head()
Как видим, в итоге получается таблица 2708×1435, где последним столбцом является тематика статьи. Теперь снабдим каждую статью ссылками на цитирование:
class_values = sorted(papers_data["subject"].unique()) class_idc = {name: id for id, name in enumerate(class_values)} paper_idc = {name: idx for idx, name in enumerate(sorted(papers_data["paper_id"].unique()))} papers_data["paper_id"] = papers_data["paper_id"].apply(lambda name: paper_idc[name]) citations_data["source"] = citations_data["source"].apply(lambda name: paper_idc[name]) citations_data["target"] = citations_data["target"].apply(lambda name: paper_idc[name]) papers_data["subject"] = papers_data["subject"].apply(lambda value: class_idc[value
Визуализация данных
Просто иметь данные не очень удобно. Лучше всего построить граф на основе полученных данных. Это можно сделать с помощью библиотеки networkx:
import networkx as nx import matplotlib.pyplot as plt plt.figure(figsize=(10, 10)) colors = papers_data["subject"].tolist() cora_graph = nx.from_pandas_edgelist(citations_data.sample(n=1500)) subjects = list(papers_data[papers_data["paper_id"].isin(list(cora_graph.nodes))]["subject"]) nx.draw_spring(cora_graph, node_size=15, node_color=subjects)
Узлом данного графа является статья, а соединение — цитированием. Цвет обозначает тематику статьи. Однако мы еще не преобразовали данные в виде графа.
Приводим данные к графовому виду
GNN требует именно графовых данных. Этим мы и займемся: преобразуем данные в виде графа. Базовый граф состоит из следующих сущностей:
- Признаки узла — это количество узлов и количество признаков в массиве. Например, в данном датасете признаками является бинарный вектор для каждой статьи.
- Ребра графа — это разреженная матрица соединений узлов. В нашем датасете это цитирования.
- Веса ребер — количество ссылающихся узла. Ведь если на узел ссылается много других узлов, то и узлы, на которые ссылается он тоже авторитетны.
Итак, исходя из этих определений построим граф данных. Признаками является вектор из слов:
import tensorflow as tf feature_names = set(papers.columns) - {"paper_id", "subject"} print(feature_names) """ {'term_331', 'term_781', 'term_123', ...} """
Узлами является цитирующий (source
) и цитируемый (target
) объект:
edges = citations_data[["source", "target"]].to_numpy().T print(edges.shape) """ (2, 5429) """
Теперь на этой основе построим признаки узла, где для каждой статьи дадим собственный мешок слов:
node_features = tf.cast( papers_data.sort_values("paper_id")[feature_names].to_numpy(), dtype=tf.dtypes.float32 ) print(node_features.shape) """ (2708, 1433) """
И, наконец, веса ребер:
edge_weights = tf.ones(shape=edges.shape[1]) print(edge_weights.shape) """ (5429,) """
Теперь мы готовые построить граф данных с использованием кортежа:
graph_info = (node_features, edges, edge_weights)
Реализация графовой нейронной сети
Графовые алгоритмы. Бизнес-приложения
Код курса
GRAF
Ближайшая дата курса
по запросу
Продолжительность
24 ак.часов
Стоимость обучения
54 000 руб.
Первое что нужно сделать — это построить графовый слой. Что нам нужно для этого сделать?
- Подготовить входные узлы с использованием нейронной сети прямого распространения.
- Обеспечить передачу сообщения (отклика) от узла до его соседей с учетом весов. В терминах математики это означает использование сочетаний инвариантного пулинга (using permutation invariant pooling).
- Создать новые состояния представления узлов. Здесь мы комбинируем представления узлов и их сообщений. Например, если этой комбинацией является GRU (Gated recurrent unit, Управляемый рекуррентный блок), тогда представление узлов и сообщений могут быть соединены вместе для создания последовательности, которая затем подается в GRU.
Для всего этого был создан с помощью Keras графовый сверточный слой, который состоит из подготовительных, комбинирующих и обновляющих функций.
Применение классификатора на основе графовой нейронной сети
После создания GNN сделаем классификатор. Он будет выполнять следующее:
- предобработку признаков узлов для генерация их представления;
- применение разработанного графового слоя;
- последующую обработку представления узлов для генерации окончательного представления;
- применение слоя с softmax для создания предсказаний.
Код с графовым слоем и классификатором можете посмотреть здесь.
Обучение модели
Инициализируем модель:
hidden_units = [32, 32] learning_rate = 0.01 dropout_rate = 0.5 num_epochs = 300 batch_size = 256 gnn_model = GNNNodeClassifier( graph_info=graph_info, num_classes=num_classes, hidden_units=hidden_units, dropout_rate=dropout_rate, name="gnn_model", ) gnn_model.summary()
Вынесем тренировочные данные:
x_train = train_data.paper_id.to_numpy() y_train = train_data["subject"]
А также создадим функцию, которая будет компилировать и обучать модель:
def run_experiment(model, x_train, y_train): model.compile( optimizer=keras.optimizers.Adam(learning_rate), loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=[keras.metrics.SparseCategoricalAccuracy(name="acc")], ) early_stopping = keras.callbacks.EarlyStopping( monitor="val_acc", patience=50, restore_best_weights=True ) history = model.fit( x=x_train, y=y_train, epochs=num_epochs, batch_size=batch_size, validation_split=0.15, callbacks=[early_stopping], ) return history history = run_experiment(gnn_model, x_train, y_train)
Визуализация результатов
Визуализация результатов
С помощью библиотеки matplotlib
построим график изменения точности с каждой эпохой:
fig, ax2 = plt.subplots(1, figsize=(15, 5)) ax2.plot(history.history["acc"]) ax2.plot(history.history["val_acc"]) ax2.legend(["train", "test"], loc="upper right") ax2.set_xlabel("Epochs") ax2.set_ylabel("Accuracy") plt.show()
Как видим, что модель обучилась и имеет точность 90% на тренировочных и 80% на тестовых данных.
При построение графовой нейронной сети лишь требуется подготовить данные и написать соответствующую архитектуру.
Еще больше о графовых нейронных сетях вы узнаете на специализированном курсе «Графовые алгоритмы. Бизнес-приложения» в лицензированном учебном центре обучения и повышения квалификации разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве.