グラフ構造化データでScikit-Learnラベル伝播を使用する方法


11

私の研究の一環として、グラフ上でラベル伝播を実行することに興味があります。私はこれらの2つの方法に特に興味があります。

scikit-learnがそのためのモデルを提供しているのを見ました。しかしながら、このモデルは、ベクター構造化データ(に適用されることになっている、すなわちデータ点)。

モデルは、カーネルを使用してデータポイントからアフィニティマトリックスを作成し、作成されたマトリックスに対してアルゴリズムを実行します。類似度行列の代わりにグラフの隣接行列を直接入力できるようにしたいと思います。

それを達成する方法について何か考えはありますか?または、前述の2つのメソッドのグラフ構造化データでラベル伝播を直接実行できるPythonライブラリを知っていますか?

よろしくお願いします!


Scikit-learnソースコードをチェックして、アフィニティマトリックスを計算した後の動作を確認しましたか?おそらく、その部分の後にコードを「コピー」して、隣接マトリックスに直接適用できます。
Tasos

ご意見をありがとうございます!したがって、実際には、これは私が現在行っていることですが、自分のニーズに合わせて変更する必要があるコードの一部はやや不可解です。これらの部分を書き換えるとエラーになります。より簡単な方法が存在することを望んでいました。
ティボーマルチネス

1
github.com/scikit-learn/scikit-learn/blob/7389dba/sklearn/…のソースコード-実装は_build_graphメソッドをオーバーライドする必要があると述べています。したがって、当然のことながら、事前計算された行列を受け入れる派生クラスを作成してみてください。
ミカライ

回答:


2

私自身の質問にここで答えます。読者の皆さんにも役立つと思います。

Scikit-learnは、主にベクター構造化データを処理するように設計されています。したがって、グラフ構造化データでラベルの伝播/ラベルの分散を実行する場合は、Scikitインターフェースを使用するよりも、メソッドを自分で再実装する方がよいでしょう。

これは、PyTorchでのラベル伝播とラベル拡散の実装です。

2つの方法は全体的に同じアルゴリズムのステップに従いますが、隣接行列の正規化方法と各ステップでのラベルの伝達方法が異なります。したがって、2つのモデルの基本クラスを作成しましょう。

from abc import abstractmethod
import torch

class BaseLabelPropagation:
    """Base class for label propagation models.

    Parameters
    ----------
    adj_matrix: torch.FloatTensor
        Adjacency matrix of the graph.
    """
    def __init__(self, adj_matrix):
        self.norm_adj_matrix = self._normalize(adj_matrix)
        self.n_nodes = adj_matrix.size(0)
        self.one_hot_labels = None 
        self.n_classes = None
        self.labeled_mask = None
        self.predictions = None

    @staticmethod
    @abstractmethod
    def _normalize(adj_matrix):
        raise NotImplementedError("_normalize must be implemented")

    @abstractmethod
    def _propagate(self):
        raise NotImplementedError("_propagate must be implemented")

    def _one_hot_encode(self, labels):
        # Get the number of classes
        classes = torch.unique(labels)
        classes = classes[classes != -1]
        self.n_classes = classes.size(0)

        # One-hot encode labeled data instances and zero rows corresponding to unlabeled instances
        unlabeled_mask = (labels == -1)
        labels = labels.clone()  # defensive copying
        labels[unlabeled_mask] = 0
        self.one_hot_labels = torch.zeros((self.n_nodes, self.n_classes), dtype=torch.float)
        self.one_hot_labels = self.one_hot_labels.scatter(1, labels.unsqueeze(1), 1)
        self.one_hot_labels[unlabeled_mask, 0] = 0

        self.labeled_mask = ~unlabeled_mask

    def fit(self, labels, max_iter, tol):
        """Fits a semi-supervised learning label propagation model.

        labels: torch.LongTensor
            Tensor of size n_nodes indicating the class number of each node.
            Unlabeled nodes are denoted with -1.
        max_iter: int
            Maximum number of iterations allowed.
        tol: float
            Convergence tolerance: threshold to consider the system at steady state.
        """
        self._one_hot_encode(labels)

        self.predictions = self.one_hot_labels.clone()
        prev_predictions = torch.zeros((self.n_nodes, self.n_classes), dtype=torch.float)

        for i in range(max_iter):
            # Stop iterations if the system is considered at a steady state
            variation = torch.abs(self.predictions - prev_predictions).sum().item()

            if variation < tol:
                print(f"The method stopped after {i} iterations, variation={variation:.4f}.")
                break

            prev_predictions = self.predictions
            self._propagate()

    def predict(self):
        return self.predictions

    def predict_classes(self):
        return self.predictions.max(dim=1).indices

モデルは、グラフの隣接行列とノードのラベルを入力として受け取ります。ラベルは、各ノードのクラス番号を示す整数のベクトルの形式で、ラベルのないノードの位置に-1が付きます。

ラベル伝播アルゴリズムを以下に示します。

W: adjacency matrix of the graph Compute the diagonal degree matrix D by DiijWij Initialize Y^(0)(y1,,yl,0,0,,0) Iterate  1. Y^(t+1)D1WY^(t) 2. Y^l(t+1)Yl until convergence to Y^() Label point xi by the sign of y^i()

Xiaojin朱とズービン・ガウラマーニー。ラベル伝播を使用して、ラベル付きデータとラベルなしデータから学習します。テクニカルレポートCMU-CALD-02-107、カーネギーメロン大学、2002年

次の実装を取得します。

class LabelPropagation(BaseLabelPropagation):
    def __init__(self, adj_matrix):
        super().__init__(adj_matrix)

    @staticmethod
    def _normalize(adj_matrix):
        """Computes D^-1 * W"""
        degs = adj_matrix.sum(dim=1)
        degs[degs == 0] = 1  # avoid division by 0 error
        return adj_matrix / degs[:, None]

    def _propagate(self):
        self.predictions = torch.matmul(self.norm_adj_matrix, self.predictions)

        # Put back already known labels
        self.predictions[self.labeled_mask] = self.one_hot_labels[self.labeled_mask]

    def fit(self, labels, max_iter=1000, tol=1e-3):
        super().fit(labels, max_iter, tol)

ラベル拡散アルゴリズムは次のとおりです。

W: adjacency matrix of the graph Compute the diagonal degree matrix D by DiijWij Compute the normalized graph Laplacian LD1/2WD1/2 Initialize Y^(0)(y1,,yl,0,0,,0) Choose a parameter α[0,1) Iterate Y^(t+1)αLY^(t)+(1α)Y^(0) until convergence to Y^() Label point xi by the sign of y^i()

Dengyong周、オリヴィエ・ブスケ、トーマス・ナビン・ラル、ジェイソン・ウェストン、ベルンハルト・Schoelkopf。ローカルおよびグローバルな一貫性を備えた学習(2004)

したがって、実装は次のようになります。

class LabelSpreading(BaseLabelPropagation):
    def __init__(self, adj_matrix):
        super().__init__(adj_matrix)
        self.alpha = None

    @staticmethod
    def _normalize(adj_matrix):
        """Computes D^-1/2 * W * D^-1/2"""
        degs = adj_matrix.sum(dim=1)
        norm = torch.pow(degs, -0.5)
        norm[torch.isinf(norm)] = 1
        return adj_matrix * norm[:, None] * norm[None, :]

    def _propagate(self):
        self.predictions = (
            self.alpha * torch.matmul(self.norm_adj_matrix, self.predictions)
            + (1 - self.alpha) * self.one_hot_labels
        )

    def fit(self, labels, max_iter=1000, tol=1e-3, alpha=0.5):
        """
        Parameters
        ----------
        alpha: float
            Clamping factor.
        """
        self.alpha = alpha
        super().fit(labels, max_iter, tol)

合成データで伝播モデルをテストしてみましょう。そのために、穴居人グラフを使用することを選択します。

import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

# Create caveman graph
n_cliques = 4
size_cliques = 10
caveman_graph = nx.connected_caveman_graph(n_cliques, size_cliques)
adj_matrix = nx.adjacency_matrix(caveman_graph).toarray()

# Create labels
labels = np.full(n_cliques * size_cliques, -1.)

# Only one node per clique is labeled. Each clique belongs to a different class.
labels[0] = 0
labels[size_cliques] = 1
labels[size_cliques * 2] = 2
labels[size_cliques * 3] = 3

# Create input tensors
adj_matrix_t = torch.FloatTensor(adj_matrix)
labels_t = torch.LongTensor(labels)

# Learn with Label Propagation
label_propagation = LabelPropagation(adj_matrix_t)
label_propagation.fit(labels_t)
label_propagation_output_labels = label_propagation.predict_classes()

# Learn with Label Spreading
label_spreading = LabelSpreading(adj_matrix_t)
label_spreading.fit(labels_t, alpha=0.8)
label_spreading_output_labels = label_spreading.predict_classes()

# Plot graphs
color_map = {-1: "orange", 0: "blue", 1: "green", 2: "red", 3: "cyan"}
input_labels_colors = [color_map[l] for l in labels]
lprop_labels_colors = [color_map[l] for l in label_propagation_output_labels.numpy()]
lspread_labels_colors = [color_map[l] for l in label_spreading_output_labels.numpy()]

plt.figure(figsize=(14, 6))
ax1 = plt.subplot(1, 4, 1)
ax2 = plt.subplot(1, 4, 2)
ax3 = plt.subplot(1, 4, 3)

ax1.title.set_text("Raw data (4 classes)")
ax2.title.set_text("Label Propagation")
ax3.title.set_text("Label Spreading")

pos = nx.spring_layout(caveman_graph)
nx.draw(caveman_graph, ax=ax1, pos=pos, node_color=input_labels_colors, node_size=50)
nx.draw(caveman_graph, ax=ax2, pos=pos, node_color=lprop_labels_colors, node_size=50)
nx.draw(caveman_graph, ax=ax3, pos=pos, node_color=lspread_labels_colors, node_size=50)

# Legend
ax4 = plt.subplot(1, 4, 4)
ax4.axis("off")
legend_colors = ["orange", "blue", "green", "red", "cyan"]
legend_labels = ["unlabeled", "class 0", "class 1", "class 2", "class 3"]
dummy_legend = [ax4.plot([], [], ls='-', c=c)[0] for c in legend_colors]
plt.legend(dummy_legend, legend_labels)

plt.show()

実装されたモデルは正しく機能し、グラフ内のコミュニティを検出できます。

穴居人グラフでテストされたラベル伝播とラベル拡散の実装

注:提示されている伝播方法は、無向グラフで使用するためのものです。

コードは、インタラクティブなJupyterノートブック としてこちらから入手できます

バインダー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.