В моей последней статье я представил концепцию графической нейронной сети (GNN) и некоторые ее недавние улучшения. Поскольку эта тема сильно раздувается, я решил сделать это руководство о том, как легко реализовать вашу нейронную сеть Graph в вашем проекте. Вы узнаете, как создать свою собственную GNN с помощью PyTorch Geometric и как использовать GNN для решения реальной проблемы (Recsys Challenge 2015).

В этом сообщении блога мы будем использовать PyTorch и PyTorch Geometric (PyG), фреймворк графической нейронной сети, построенный на основе PyTorch, который работает невероятно быстро. Он в несколько раз быстрее, чем самый известный фреймворк GNN, DGL.

Помимо замечательной скорости, PyG поставляется с набором хорошо реализованных моделей GNN, проиллюстрированных в различных статьях. Поэтому было бы очень удобно воспроизвести эксперименты с PyG.

Без сомнения, учитывая его преимущество в скорости и удобстве, PyG является одной из самых популярных и широко используемых библиотек GNN. Давайте погрузимся в тему и запачкаем руки!

Требования

  • PyTorch - 1.1.0
  • PyTorch Geometric - 1.2.0

Геометрические основы PyTorch

В этом разделе вы познакомитесь с основами PyG. По сути, он будет охватывать torch_geometric.data и torch_geometric.nn. Вы узнаете, как передавать геометрические данные в вашу GNN, и как разработать собственный уровень MessagePassing, являющийся ядром GNN.

Данные

Модуль torch_geometric.data содержит класс данных, который позволяет очень легко создавать графики из ваших данных. Вам нужно только указать:

  1. атрибуты / функции, связанные с каждым узлом
  2. связь / смежность каждого узла (индекс края)

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

Итак, в графе 4 узла, v1… v4, каждый из которых связан с двумерным вектором признаков, и метка y, указывающая его класс. Эти два могут быть представлены как FloatTensors:

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

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

Собрав их вместе, мы можем создать объект Data, как показано ниже:

Набор данных

Процедура создания набора данных не очень проста, но может показаться знакомой тем, кто использовал torchvision, поскольку PyG следует его соглашению. PyG предоставляет два разных типа классов наборов данных: InMemoryDataset и Dataset. Как они буквально указывают, первый предназначен для данных, которые умещаются в вашей оперативной памяти, а второй - для данных гораздо большего размера. Поскольку их реализации очень похожи, я буду рассматривать только InMemoryDataset.

Чтобы создать объект InMemoryDataset, вам необходимо реализовать 4 функции:

  • raw_file_names ()

Он возвращает список, который показывает список необработанных, необработанных имен файлов. Если у вас есть только файл, то возвращаемый список должен содержать только 1 элемент. Фактически, вы можете просто вернуть пустой список и указать свой файл позже в process ().

  • имя_файла_обработки ()

Подобно последней функции, она также возвращает список, содержащий имена файлов всех обработанных данных. После вызова process () обычно возвращаемый список должен содержать только один элемент, в котором хранится единственное имя файла обработанных данных.

  • скачать ()

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

pass

в функции.

  • процесс ()

Это самый важный метод набора данных. Вам необходимо собрать свои данные в список объектов данных. Затем вызовите self.collate (), чтобы вычислить срезы, которые будут использоваться объектом DataLoader. Ниже показан пример настраиваемого набора данных с официального сайта PyG.

Позже в этой статье я покажу вам, как создать собственный набор данных из данных, предоставленных в RecSys Challenge 2015.

DataLoader

Класс DataLoader позволяет без особых усилий загружать данные в модель. Чтобы создать объект DataLoader, вы просто указываете набор данных и желаемый размер пакета.

loader = DataLoader(dataset, batch_size=512, shuffle=True)

Каждая итерация объекта DataLoader дает объект Batch, который очень похож на объект Data, но с атрибутом «batch». Он указывает, с каким графом связан каждый узел. Поскольку DataLoader объединяет x, y и edge_index из разных выборок / графиков в пакеты, модели GNN нужна эта «пакетная» информация, чтобы знать какие узлы принадлежат одному и тому же графу в пакете для выполнения вычислений.

for batch in loader:
    batch
    >>> Batch(x=[1024, 21], edge_index=[2, 1568], y=[512], batch=[1024])

Сообщение

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

x обозначает вложения узлов, e обозначает граничные элементы, 𝜙 обозначает функцию сообщения, □ обозначает агрегирование , 𝛾 обозначает функцию обновления. Если ребра в графе не имеют других свойств, кроме связности, e, по сути, является индексом ребра графа. Верхний индекс представляет индекс слоя. Когда k = 1, x представляет входную функцию каждого узла. Ниже я проиллюстрирую, как работает каждая функция:

  • распространять (edge_index, size = None, ** kwargs):

Он принимает индекс края и другую дополнительную информацию, такую ​​как особенности узла (встраивание). Вызов этой функции соответственно вызовет message и update.

  • сообщение (** kwargs):

Вы определяете, как вы создаете «сообщение» для каждой пары узлов (x_i, x_j). Поскольку он следует за вызовами распространять, он может принимать любой аргумент, передаваемый в распространять. Следует отметить, что вы можете определить отображение аргументов в конкретные узлы с помощью «_i» и «_j». Следовательно, вы должны быть очень осторожны при именовании аргумента этой функции.

  • update (aggr_out, ** kwargs)

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

Пример

Давайте посмотрим, как мы можем реализовать слой SageConv из статьи « Обучение индуктивному представлению на больших графах ». Формула передачи сообщений SageConv определяется как:

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

который показывает, как строится «сообщение». Вложение каждого соседнего узла умножается на матрицу весов, добавляется смещение и передается через функцию активации. Это легко сделать с помощью torch.nn.Linear.

class SAGEConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super(SAGEConv, self).__init__(aggr='max')
        self.lin = torch.nn.Linear(in_channels, out_channels)
        self.act = torch.nn.ReLU()
        
    def message(self, x_j):
        # x_j has shape [E, in_channels]

        x_j = self.lin(x_j)
        x_j = self.act(x_j)
      
        return x_j

Что касается части обновления, агрегированное сообщение и встраивание текущего узла агрегируются. Затем он умножается на другую матрицу весов и применяется другая функция активации.

class SAGEConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super(SAGEConv, self).__init__(aggr='max')
        self.update_lin = torch.nn.Linear(in_channels + out_channels, in_channels, bias=False)
        self.update_act = torch.nn.ReLU()
        
    def update(self, aggr_out, x):
        # aggr_out has shape [N, out_channels]
        
        new_embedding = torch.cat([aggr_out, x], dim=1)
        new_embedding = self.update_lin(new_embedding)
        new_embedding = torch.update_act(new_embedding)
        
        return new_embedding

Собирая все вместе, мы получаем следующий слой SageConv.

Пример из реальной жизни - RecSys Challenge 2015

RecSys Challenge 2015 предлагает специалистам по обработке данных создать систему рекомендаций на основе сеансов. Участникам этого задания предлагается решить две задачи:

  1. Предскажите, будет ли событие покупки, за которым последует последовательность кликов
  2. Предскажите, какой товар будет куплен

Сначала мы загружаем данные с официального сайта RecSys Challenge 2015 и строим Dataset. Начнем с первого задания, так как это проще.

Задача предоставляет два основных набора данных, yoochoose-clicks.dat и yoochoose-buys.dat, которые содержат события кликов и события покупки, соответственно. Давайте быстро просмотрим данные:

Предварительная обработка

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

Поскольку данные довольно большие, мы делаем их для упрощения демонстрации.

Чтобы определить основную истину, то есть есть ли какое-либо событие покупки для данного сеанса, мы просто проверяем, присутствует ли session_id в yoochoose-clicks.dat в yoochoose-buys.dat а также.

Построение набора данных

Данные готовы к преобразованию в объект набора данных после этапа предварительной обработки. Здесь мы рассматриваем каждый элемент в сеансе как узел, и поэтому все элементы в одном сеансе образуют граф. Чтобы создать набор данных, мы группируем предварительно обработанные данные по session_id и перебираем эти группы. На каждой итерации item_id в каждой группе снова категорично кодируется, поскольку для каждого графа индекс узла должен отсчитываться от 0. Таким образом, мы имеем следующее:

После создания набора данных мы вызываем shuffle (), чтобы убедиться, что он был перемешан случайным образом, а затем разбиваем его на три набора для обучения, проверки и тестирования.

Создайте графическую нейронную сеть

Следующая пользовательская GNN берет ссылку из одного из примеров в официальном репозитории PyG на Github. Я изменил слой GraphConv на наш самореализованный слой SAGEConv, показанный выше. Кроме того, выходной слой также был изменен в соответствии с настройкой двоичной классификации.

Обучение

Обучить нашу пользовательскую GNN очень просто, мы просто повторяем DataLoader, созданный из обучающего набора, и распространяем функцию потерь в обратном направлении. Здесь мы используем Адама в качестве оптимизатора со скоростью обучения, установленной на 0,005, и двоичной кросс-энтропией в качестве функции потерь.

Проверка

Этот ярлык очень несбалансирован с подавляющим количеством отрицательных ярлыков, поскольку за большинством сессий не следует никаких событий покупки. Другими словами, глупая модель, предполагающая все негативы, даст вам точность более 90%. Таким образом, вместо точности показатель «Площадь под кривой» (AUC) является лучшим показателем для этой задачи, поскольку его интересует только то, если положительные примеры оцениваются выше, чем отрицательные. Мы используем стандартную функцию расчета AUC от Sklearn.

Результат

Я обучил модель для 1 эпохи и измерил оценки AUC для обучения, проверки и тестирования:

Имея всего 1 миллион строк обучающих данных (около 10% всех данных) и 1 эпоху обучения, мы можем получить оценку AUC около 0,73 для проверки и набора тестов. Оценка, скорее всего, улучшится, если для обучения модели будет использоваться больше данных с большими шагами обучения.

Заключение

Вы изучили основы использования PyTorch Geometric, включая построение набора данных, настраиваемый слой графа и обучение GNN с реальными данными. Весь код в этом посте также можно найти в моем репозитории Github, где вы можете найти еще один файл записной книжки Jupyter, в котором я решаю вторую задачу RecSys Challenge 2015. Надеюсь, вам понравилась эта статья. Если у вас есть вопросы или комментарии, оставьте их ниже! Не забудьте подписаться на меня в твиттере, где я делюсь своим сообщением в блоге или интересными новостями машинного обучения / глубокого обучения! Удачи, играя в GNN с PyG!