Графовые нейронные сети с нуля

автор рубрика
Графовые нейронные сети с нуля

Графовые нейронные сети (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()
DataFrame с мешком слов
Некоторые слова и тематики статей

Как видим, в итоге получается таблица 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)

 

Граф, полученный с помощью networkx
Визуализация графа

Узлом данного графа является статья, а соединение — цитированием. Цвет обозначает тематику статьи. Однако мы еще не преобразовали данные в виде графа.

Приводим данные к графовому виду

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
Ближайшая дата курса
25 июля, 2022
Длительность обучения
24 ак.часов
Стоимость обучения
60 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()
Как выглядит модель GNN
Архитектура графовой нейронной сети

Вынесем тренировочные данные:

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()
График точности GNN
График точности

Как видим, что модель обучилась и имеет точность 90% на тренировочных и 80% на тестовых данных.

При построение графовой нейронной сети лишь требуется подготовить данные и написать соответствующую архитектуру.

 

Еще больше о графовых нейронных сетях вы узнаете на специализированном курсе «Графовые алгоритмы. Бизнес-приложения» в лицензированном учебном центре обучения и повышения квалификации разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве.

Источники
  1. Оригинал статьи
Комментировать