วิธีใช้ Scikit-Learn การเผยแพร่ฉลากบนข้อมูลที่มีโครงสร้างของกราฟ


11

เป็นส่วนหนึ่งของการวิจัยของฉันฉันสนใจที่จะทำการเผยแพร่ฉลากบนกราฟ ฉันสนใจวิธีการทั้งสองนี้เป็นพิเศษ:

  • เซี่ยวจินจู้และซู่บิน Ghahramani เรียนรู้จากข้อมูลที่ติดป้ายกำกับและไม่มีป้ายกำกับด้วยการเผยแพร่ฉลาก รายงานทางเทคนิค CMU-CALD-02-107, Carnegie Mellon University, 2002 http://pages.cs.wisc.edu/~jerryzhu/pub/CMU-CALD-02-107.pdf
  • Dengyong Zhou, Olivier Bousquet, Thomas Navin Lal, Jason Weston, Bernhard Schoelkopf การเรียนรู้ด้วยความสอดคล้องระดับท้องถิ่นและระดับโลก (2004) http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.115.3219

ฉันเห็นว่า scikit เรียนรู้นำเสนอรูปแบบการทำเช่นนั้น อย่างไรก็ตามโมเดลนี้ควรใช้กับข้อมูลที่มีโครงสร้างแบบเวกเตอร์ ( เช่นจุดข้อมูล)

โมเดลสร้างเมทริกซ์ความสัมพันธ์จากจุดข้อมูลโดยใช้เคอร์เนลจากนั้นเรียกใช้อัลกอริทึมบนเมทริกที่สร้างขึ้น ฉันต้องการที่จะป้อนเมทริกซ์ adjacency โดยตรงของกราฟของฉันแทนเมทริกซ์ความเหมือนกัน

มีความคิดเกี่ยวกับวิธีการบรรลุผลนั้น หรือคุณรู้จักห้องสมุด Python ใด ๆ ที่จะอนุญาตให้เรียกใช้การเผยแพร่ฉลากโดยตรงบนข้อมูลโครงสร้างของกราฟสำหรับสองวิธีดังกล่าวข้างต้น

ขอบคุณล่วงหน้าสำหรับความช่วยเหลือของ!


คุณได้ตรวจสอบซอร์สโค้ดของ Scikit-Learn แล้วเพื่อดูว่ามันทำอะไรหลังจากการคำนวณเมทริกซ์ความสัมพันธ์? อาจจะ "คัดลอก" รหัสหลังจากส่วนนั้นเพื่อนำไปใช้โดยตรงกับเมทริกซ์ adjacency ของคุณ
Tasos

ขอบคุณสำหรับความคิดเห็นของคุณ! ดังนั้นที่จริงแล้วนี่คือสิ่งที่ฉันกำลังทำอยู่ แต่บางส่วนของรหัสที่ฉันต้องแก้ไขเพื่อให้เหมาะกับความต้องการของฉันค่อนข้างคลุมเครือ ฉันกลัวว่าการเขียนซ้ำส่วนเหล่านั้นจะนำไปสู่ข้อผิดพลาด ฉันหวังว่าจะมีวิธีการที่ตรงไปตรงมามากขึ้น
Thibaud Martinez

1
ซอร์สโค้ดที่github.com/scikit-learn/scikit-learn/blob/7389dba/sklearn/… - กล่าวว่าการติดตั้งใช้งานควรจะแทนที่เมธอด _build_graph ดังนั้นคุณควรลองสร้างคลาสที่ได้รับซึ่งยอมรับเมทริกซ์ที่คำนวณล่วงหน้าได้
mikalai

คำตอบ:


2

ตอบคำถามของฉันเองที่นี่เพราะฉันหวังว่ามันจะเป็นประโยชน์สำหรับผู้อ่านบางคน

Scikit-learning ได้รับการออกแบบมาเพื่อจัดการกับข้อมูลที่มีโครงสร้างแบบเวกเตอร์เป็นหลัก ดังนั้นหากคุณต้องการดำเนินการเผยแพร่ฉลาก / การกระจายฉลากบนข้อมูลที่มีโครงสร้างแบบกราฟคุณอาจจะดีกว่าที่จะปรับใช้วิธีการด้วยตัวเองแทนที่จะใช้ส่วนติดต่อ Scikit

นี่คือการดำเนินการตามป้ายเผยแพร่และป้ายกระจายใน PyTorch

ทั้งสองวิธีโดยรวมปฏิบัติตามขั้นตอนวิธีเดียวกันโดยมีการแปรผันว่าเมทริกซ์ adjacency เป็นมาตรฐานและวิธีแพร่กระจายเลเบลในแต่ละขั้นตอน ลองสร้างคลาสพื้นฐานสำหรับสองโมเดลของเรา

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

ตัวแบบใช้เป็นอินพุตเมทริกซ์ adjacency ของกราฟเช่นเดียวกับเลเบลของโหนด เลเบลอยู่ในรูปแบบของเวกเตอร์ของเลขจำนวนเต็มที่ระบุหมายเลขคลาสของแต่ละโหนดด้วย -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 Zhu และ Zoubin Ghahramani เรียนรู้จากข้อมูลที่ติดป้ายกำกับและไม่มีป้ายกำกับด้วยการเผยแพร่ฉลาก รายงานทางเทคนิค CMU-CALD-02-107, Carnegie Mellon University, 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 Zhou, Olivier Bousquet, Thomas Navin Lal, Jason Weston, Bernhard 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.