2025, Nov 07 21:01

Как отобразить метки признаков узлов в NetworkX из torch_geometric

Показываем, как отрисовать в NetworkX метки узлов из torch_geometric: словарь labels, конвертация тензоров в строки и применение draw_networkx с примерами

Отрисовка признаков узлов на графике NetworkX кажется обманчиво простой, однако многие замечают, что стандартная визуализация показывает только индексы узлов. Если ваши данные приходят из torch_geometric и вы ожидаете увидеть на узлах значения вроде -1, 0 и 1, эти метки нужно явно передать процедуре рисования.

Как воспроизвести проблему

Ниже приведён небольшой пример, который строит крошечный граф и пытается его визуализировать. Отрисовка проходит успешно, но на узлах не печатаются их признаки — видны только числовые идентификаторы.

import torch
from torch_geometric.data import Data
edges_idx = torch.tensor(
    [
        [0, 1, 1, 2],
        [1, 0, 2, 1]
    ],
    dtype=torch.long
)
# по одному скалярному признаку на узел
data_feats = torch.tensor([[-1], [0], [1]], dtype=torch.float)
graph_data = Data(x=data_feats, edge_index=edges_idx)
show_graph = True
if show_graph:
    import networkx as nx
    import matplotlib.pyplot as plt
    edge_pairs = edges_idx.t().tolist()
    graph_obj = nx.Graph()
    graph_obj.add_edges_from(edge_pairs)
    graph_obj.add_nodes_from(range(data_feats.size(0)))
    coords = nx.spring_layout(graph_obj, seed=1)
    plt.figure(figsize=(6, 6))
    nx.draw_networkx(
        graph_obj,
        coords,
        with_labels=True,
        node_color='lightblue',
        edge_color='gray',
        node_size=800
    )
    plt.title("Visualization of the Social Network Graph")
    plt.axis('off')
    plt.show()

Что происходит на самом деле

NetworkX ничего не знает о ваших признаках из torch_geometric, пока вы их явно не передадите. Узлы в этом графе создаются как простые целые числа, поэтому with_labels=True приводит к отображению именно этих чисел. Чтобы увидеть значения признаков, сопоставьте идентификаторы узлов нужному тексту и передайте это соответствие через labels=...

Решение: передайте словарь labels

Создайте словарь, который мапит id узла к нужной строке. Если признаки хранятся в тензорах, сначала преобразуйте их в питоновские объекты. Использование .int() помогает избежать строк вроде [1.0], если вы предпочитаете [1]. Затем укажите labels=... в draw_networkx или нарисуйте метки отдельно через draw_networkx_labels.

import torch
from torch_geometric.data import Data
edges_idx = torch.tensor(
    [
        [0, 1, 1, 2],
        [1, 0, 2, 1]
    ],
    dtype=torch.long
)
data_feats = torch.tensor([[-1], [0], [1]], dtype=torch.float)
graph_data = Data(x=data_feats, edge_index=edges_idx)
# Формируем метки: {node_id: текст_признака}
# Используем .int(), чтобы избежать меток вида [1.0]
label_map = dict(enumerate(data_feats.int().tolist()))
print(label_map)
show_graph = True
if show_graph:
    import networkx as nx
    import matplotlib.pyplot as plt
    edge_pairs = edges_idx.t().tolist()
    graph_obj = nx.Graph()
    graph_obj.add_edges_from(edge_pairs)
    graph_obj.add_nodes_from(range(data_feats.size(0)))
    coords = nx.spring_layout(graph_obj, seed=1)
    plt.figure(figsize=(6, 6))
    # Вариант A: передать labels напрямую
    nx.draw_networkx(
        graph_obj,
        coords,
        node_color='lightblue',
        edge_color='gray',
        node_size=800,
        with_labels=True,
        labels=label_map
    )
    # Вариант B: рисовать метки отдельно
    # nx.draw_networkx(
    #     graph_obj,
    #     coords,
    #     node_color='lightblue',
    #     edge_color='gray',
    #     node_size=800,
    #     with_labels=False
    # )
    # nx.draw_networkx_labels(graph_obj, coords, labels=label_map)
    plt.title("Visualization of the Social Network Graph")
    plt.axis('off')
    plt.show()

Форматируем метки так, как нужно

Поскольку метки — это обычные строки, вы можете оформить их так, как удобно для вашей визуализации. Если хотите добавить поясняющий текст или несколько строк, переводите значения в строки и используйте символы перевода строки. Это также упрощает вставку LaTeX, если ваша конфигурация Matplotlib его поддерживает.

label_map = {idx: f"x = {item}\ny = $2^{{ {item[0]} }}$" for idx, item in enumerate(data_feats.int().tolist())}

Можно оставить всё минимальным и просто привести значения признаков к строкам как есть.

label_map = {idx: str(item) for idx, item in enumerate(data_feats.int().tolist())}

Смежные преобразования

Если ваши данные уже лежат в объекте torch_geometric Data, вы можете напрямую построить граф NetworkX. При отрисовке метки всё равно нужно передавать отдельно.

import torch_geometric
nx_graph = torch_geometric.utils.to_networkx(graph_data, to_undirected=True)

Почему это важно

Графы часто несут богатую поузловую информацию. Если визуализация скрывает этот контекст, сложнее быстро проверять конвейеры данных и отлаживать входы модели. Явное передавание отображаемых меток сохраняет разделение ответственности: структура графа остаётся чистой, а слой визуализации решает, какой текст показывать.

Главные выводы

При построении графиков в NetworkX идентификаторы узлов — это не то же самое, что признаки узлов. Чтобы вывести признаки на рисунке, соберите словарь от id узла к тексту метки и передайте его в labels=... или используйте draw_networkx_labels. Сначала конвертируйте тензоры в питоновские типы, при необходимости приводите к int, чтобы убрать десятичные точки, и форматируйте строки для многострочных или расширенных меток. Если вы преобразуете граф из torch_geometric в NetworkX, принцип тот же: метки нужно создавать и передавать явно.

Статья основана на вопросе на StackOverflow от Mathieu Krisztian и ответе пользователя furas.