paint-brush
การคาดการณ์ลิงก์ในกราฟด้วย Graph Neural Networks และ DGL.aiโดย@andrei9735
ประวัติศาสตร์ใหม่

การคาดการณ์ลิงก์ในกราฟด้วย Graph Neural Networks และ DGL.ai

โดย Andrei14m2024/11/06
Read on Terminal Reader

นานเกินไป; อ่าน

เรียนรู้การสร้างแบบจำลองเพื่อทำนายลิงก์ใหม่ในกราฟโซเชียล โดยสาธิตการใช้เครือข่ายประสาทกราฟและ DGL เพื่อทำนายลิงก์
featured image - การคาดการณ์ลิงก์ในกราฟด้วย Graph Neural Networks และ DGL.ai
Andrei HackerNoon profile picture


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


การทำนายลิงก์ เป็นหนึ่งในงานพื้นฐานในการวิเคราะห์กราฟ ซึ่งเกี่ยวข้องกับการทำนายการเชื่อมต่อ (หรือลิงก์) ระหว่างโหนด (เอนทิตีที่แสดงในกราฟ) ลองนึกภาพการแนะนำเพื่อนใหม่ในเครือข่ายสังคม การทำนายความร่วมมือที่อาจเกิดขึ้นในกราฟการอ้างอิงทางวิชาการ หรือการคาดการณ์การโต้ตอบในอนาคตระหว่างผู้ใช้และผลิตภัณฑ์ในการตั้งค่าอีคอมเมิร์ซ นี่คือตัวอย่างการทำนายลิงก์ในการดำเนินการ งานนี้ช่วยขยายเครือข่าย อนุมานข้อมูลที่ขาดหายไป และตรวจจับความผิดปกติ สำหรับแอปพลิเคชันตั้งแต่การปรับปรุงประสบการณ์ของผู้ใช้ไปจนถึงการปรับปรุงการตรวจจับการฉ้อโกง การทำนายลิงก์เป็นส่วนผสมสำคัญสำหรับความสำเร็จ


เพื่อแสดงการคาดการณ์ลิงก์ เราจะใช้ ชุดข้อมูลเครือข่ายสังคม Twitch จาก Stanford Network Analysis Project (SNAP) ชุดข้อมูลนี้รวบรวมการเชื่อมต่อทางสังคมระหว่างผู้ใช้บนแพลตฟอร์มสตรีมมิ่ง Twitch โดยที่โหนดแสดงถึงผู้ใช้ Twitch และเอจแสดงถึงมิตรภาพระหว่างโหนดเหล่านั้น ชุดข้อมูลมีโครงสร้างที่ดี ทำให้ง่ายต่อการประมวลผลล่วงหน้าและใช้งาน


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

เครือข่ายประสาทกราฟและไลบรารีกราฟเชิงลึก

การทำงานกับข้อมูลที่มีโครงสร้างเป็นกราฟนั้นท้าทายไม่ซ้ำใคร และนี่คือที่มาของ Graph Neural Networks (GNNs) GNN เป็นเครือข่ายประสาทชนิดหนึ่งที่ออกแบบมาโดยเฉพาะเพื่อทำงานกับข้อมูลกราฟ ซึ่งแตกต่างจากเครือข่ายประสาทแบบเดิมที่ทำงานบนอินพุตที่มีขนาดคงที่ GNN สามารถจัดการกับขนาดกราฟตามอำเภอใจและใช้ประโยชน์จากรูปแบบการเชื่อมต่อภายในข้อมูลได้ ด้วยการรวบรวมข้อมูลจากเพื่อนบ้านของโหนด GNN จึงเรียนรู้การแสดงที่จับภาพทั้งแอตทริบิวต์ของโหนดและโครงสร้างของกราฟ ทำให้มีประสิทธิภาพสูงสำหรับงานต่างๆ เช่น การจำแนกโหนด การทำนายลิงก์ และการจำแนกกราฟ


Deep Graph Library ( DGL.ai ) เป็นชุดเครื่องมืออันทรงพลังสำหรับการสร้าง GNN ได้อย่างง่ายดายและมีประสิทธิภาพ ด้วย DGL นักพัฒนาสามารถใช้ประโยชน์จากสถาปัตยกรรม GNN ที่ทันสมัยเพื่อจัดการกับงานต่างๆ รวมถึงการทำนายลิงก์ DGL นำเสนอยูทิลิตี้ต่างๆ สำหรับการทำงานกับกราฟที่เป็นเนื้อเดียวกันและไม่เป็นเนื้อเดียวกัน ทำให้เป็นเครื่องมืออเนกประสงค์สำหรับนักวิจัยและผู้ปฏิบัติงาน ด้วยการทำให้การนำ GNN ไปใช้ง่ายขึ้น DGL ช่วยให้คุณสามารถมุ่งเน้นไปที่การพัฒนาโซลูชันที่สร้างสรรค์มากขึ้น แทนที่จะจมอยู่กับความซับซ้อนทางเทคนิคพื้นฐาน


เมื่อคำนึงถึงรากฐานนี้แล้ว มาสร้างแบบจำลองการทำนายลิงก์โดยใช้ GNN และ DGL.ai กัน

การตั้งค่าโครงการ

ขั้นตอนแรกคือการตั้งค่าโครงการของเราโดยนำเข้าไลบรารีที่จำเป็น:

 import json import numpy as np import pandas as pd import dgl from dgl.data import DGLDataset from dgl.nn import SAGEConv import torch import torch.nn as nn from torch.nn.functional import binary_cross_entropy_with_logits, relu, dropout from torch.nn.utils import clip_grad_norm_ from torch.optim.lr_scheduler import ReduceLROnPlateau import itertools import scipy.sparse as sp from sklearn.metrics import roc_auc_score

การประมวลผลข้อมูลเบื้องต้นและการแยกระหว่างการฝึกและการทดสอบ

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


  1. การโหลดข้อมูลกราฟ : ในฟังก์ชันกระบวนการ เราเริ่มต้นด้วยการอ่านรายการขอบจากชุดข้อมูล เนื่องจากขอบในชุดข้อมูลดั้งเดิมไม่มีทิศทาง (แสดงถึงมิตรภาพซึ่งกันและกัน) เราจึงเพิ่มขอบย้อนกลับเพื่อให้แน่ใจว่าการเชื่อมต่อมีทิศทางสองทางในกราฟของเรา
  2. การโหลดคุณลักษณะของโหนด : ต่อไป เราจะโหลดคุณลักษณะของโหนดจากไฟล์ JSON แต่ละโหนดจะมีรายการคุณลักษณะที่มีความยาวแตกต่างกัน ดังนั้นเราจึงเพิ่มศูนย์ในรายการคุณลักษณะที่สั้นกว่าเพื่อรักษาความยาวที่สม่ำเสมอกันในทุกโหนด


นี่คือโค้ดสำหรับสร้างและประมวลผลข้อมูลล่วงหน้า:

 # create a dataset that inherits DGLDataset class SocialNetworkDataset(DGLDataset): def __init__(self): super().__init__(name='social_network') def process(self): # load edges edges_df = pd.read_csv('./twitch/ENGB/musae_ENGB_edges.csv') # ensure edges are bidirectional edges_df_rev = edges_df.copy() edges_df_rev.columns = ['to', 'from'] edges_df_rev = edges_df_rev[['from', 'to']] edges_df = pd.concat([edges_df, edges_df_rev], ignore_index=True) edges_df.drop_duplicates(inplace=True) # create a graph using DGL max_node_id = max(edges_df['from'].max(), edges_df['to'].max()) edges_src = torch.from_numpy(edges_df['from'].to_numpy()) edges_dst = torch.from_numpy(edges_df['to'].to_numpy()) self.graph = dgl.graph( (edges_src, edges_dst), num_nodes=max_node_id + 1, ) # load and node features with open('./twitch/ENGB/musae_ENGB_features.json') as f: node_features_dict = json.load(f) # feature lists have various lengths, pad them with zeros max_feature_list_len = max([len(l) for l in node_features_dict.values()]) for k in node_features_dict: if len(node_features_dict[k]) < max_feature_list_len: node_features_dict[k] += [0] * (max_feature_list_len - len(node_features_dict[k])) # set node features in graph node_features_df = pd.DataFrame.from_dict(node_features_dict).T.astype('float64') node_features_np = node_features_df.to_numpy() self.graph.ndata['feat'] = torch.from_numpy(node_features_np).float() def __len__(self): return 1 # only the whole graph is returned def __getitem__(self, idx): return self.graph


ตอนนี้เรากำลังเริ่มต้นชุดข้อมูลของเราเพื่อโหลดข้อมูลกราฟ

 # init the dataset dataset = SocialNetworkDataset() g = dataset[0]


ขั้นตอนต่อไปคือ การสร้างชุดฝึกอบรมและการทดสอบ เราจะแบ่งขอบออกเป็น อัตราส่วน 80/20 สำหรับการฝึกอบรมและการทดสอบ เราสร้างทั้งตัวอย่าง เชิงบวก (ขอบที่มีอยู่) และตัวอย่างเชิงลบ (ขอบที่ไม่มีอยู่) สำหรับทั้งสองชุด สำหรับกราฟขนาดใหญ่ ยูทิลิตี้ dgl.sampling ของ DGL อาจเป็นประโยชน์ แต่ในกรณีนี้ กราฟทั้งหมดจะพอดีกับหน่วยความจำ นี่คือโค้ดสำหรับสร้างชุดฝึกอบรมและการทดสอบ:


 # pick edges for train and test sets (80/20 split) # (for larger graphs, we can use dgl.sampling.negative etc) u, v = g.edges() edge_ids = np.random.permutation(g.num_edges()) test_set_size = int(len(edge_ids) * 0.2) train_set_size = len(edge_ids) - test_set_size # positive samples: existing edges test_positive_u, test_positive_v = u[edge_ids[:test_set_size]], v[edge_ids[:test_set_size]] train_positive_u, train_positive_v = u[edge_ids[test_set_size:]], v[edge_ids[test_set_size:]] # negative samples: nonexistent edges adj = sp.coo_matrix((np.ones(len(u)), (u.numpy(), v.numpy()))) adj_negative = 1 - adj.todense() - np.eye(g.num_nodes()) negative_u, negative_v = np.where(adj_negative != 0) negative_edge_ids = np.random.choice(len(negative_u), g.num_edges()) test_negative_u, test_negative_v = ( negative_u[negative_edge_ids[:test_set_size]], negative_v[negative_edge_ids[:test_set_size]], ) train_negative_u, train_negative_v = ( negative_u[negative_edge_ids[test_set_size:]], negative_v[negative_edge_ids[test_set_size:]], ) # create a training graph by copying the original graph and removing test edges train_g = dgl.remove_edges(g, edge_ids[:test_set_size]) # define positive and negative graphs for train and test sets train_positive_g = dgl.graph((train_positive_u, train_positive_v), num_nodes=g.num_nodes()) train_negative_g = dgl.graph((train_negative_u, train_negative_v), num_nodes=g.num_nodes()) test_positive_g = dgl.graph((test_positive_u, test_positive_v), num_nodes=g.num_nodes()) test_negative_g = dgl.graph((test_negative_u, test_negative_v), num_nodes=g.num_nodes())

โมเดล: GraphSAGE Convolutional Layers และ MLP Predictor

เราจะใช้เครือข่ายประสาทเทียม แบบกราฟตัวอย่างและการรวม (GraphSAGE) เพื่อเรียนรู้การแสดงโหนด ซึ่งเรียกอีกอย่างว่า การฝังตัว ซึ่งจะจับทั้งโครงสร้างและคุณสมบัติของแต่ละโหนดภายในกราฟ GraphSAGE ทำงานโดยรวบรวมข้อมูลคุณสมบัติจากเพื่อนบ้านของแต่ละโหนดเพื่อสร้างการแสดงที่มีความหมายสำหรับแต่ละโหนด กระบวนการนี้เรียกว่า การรวมเพื่อนบ้าน ซึ่งช่วยให้โมเดลสามารถเรียนรู้รูปแบบเฉพาะที่หลากหลายในกราฟได้


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


เพื่อปรับปรุงประสิทธิภาพของโมเดลและลดการติดตั้งเกิน เราจะใช้ การดร็อปเอาต์ หลังจากแต่ละเลเยอร์


ตอนนี้เรามาสร้างโมเดล GraphSAGE ที่มี เลเยอร์การบิดเบือนสามเลเยอร์ พร้อมด้วยฟังก์ชัน forward เพื่อกำหนดว่าข้อมูลจะไหลผ่านเลเยอร์นั้นอย่างไร:


 # define the GraphSAGE model with 3 convolutional layers class GraphSAGE(nn.Module): def __init__( self, input_features, hidden_features, output_features, dropout_probability=0.3, ): super(GraphSAGE, self).__init__() self.conv_layer_1 = SAGEConv(input_features, hidden_features, "mean") self.conv_layer_2 = SAGEConv(hidden_features, hidden_features, "mean") self.conv_layer_3 = SAGEConv(hidden_features, output_features, "mean") self.dropout_probability = dropout_probability def forward(self, graph, input_features): # first layer with ReLU activation and dropout h = relu(self.conv_layer_1(graph, input_features)) h = dropout(h, p=self.dropout_probability) # second layer with ReLU activation and dropout h = relu(self.conv_layer_2(graph, h)) h = dropout(h, p=self.dropout_probability) # third layer without dropout h = self.conv_layer_3(graph, h) return h


ผลลัพธ์หลังจากเลเยอร์ที่สาม ( h ) ประกอบด้วยการฝังตัวของโหนด เพื่อทำนายความน่าจะเป็นของขอบ (หรือลิงก์) ระหว่างโหนดสองโหนด เราจะใช้ ตัวทำนายมัลติเลเยอร์เพอร์เซพตรอน (MLP) MLP นี้ใช้การฝังตัวของโหนดสองโหนดเป็นอินพุต และคำนวณ คะแนน ที่ระบุความน่าจะเป็นของขอบที่มีอยู่ระหว่างโหนดทั้งสอง


 # define the MLP predictor class MLPPredictor(nn.Module): def __init__(self, hidden_features): super().__init__() # first linear layer to combine node embeddings self.W1 = nn.Linear(hidden_features * 2, hidden_features) # second linear layer to produce a single score output self.W2 = nn.Linear(hidden_features, 1) def apply_edges(self, edges): # concatenate source and destination node embeddings h = torch.cat([edges.src["h"], edges.dst["h"]], dim=1) # pass through MLP layers to get the edge score score = self.W2(relu(self.W1(h))).squeeze(1) return {'score': score} def forward(self, g, h): with g.local_scope(): g.ndata["h"] = h g.apply_edges(self.apply_edges) return g.edata["score"]


ตัวทำนาย MLP ทำงานดังต่อไปนี้:

  • ขนาดอินพุต: ขนาดอินพุตของตัวทำนายจะขึ้นอยู่กับการฝังตัวของโหนดที่เรียนรู้ เนื่องจากแต่ละขอบเชื่อมต่อโหนดสองโหนด เราจึงเชื่อมโยงการฝังตัวของโหนดเหล่านั้นเข้าด้วยกัน (แต่ละโหนดมีขนาดเท่ากับ hidden_features) ส่งผลให้ขนาดอินพุตเท่ากับ hidden_features * 2
  • เลเยอร์ 1 (W1): เลเยอร์นี้ประมวลผลการฝังแบบเชื่อมโยงและ ลดมิติเอาต์พุต กลับเป็น hidden_features โดยรักษาข้อมูลเชิงสัมพันธ์ที่สำคัญระหว่างโหนดไว้
  • เลเยอร์ 2 (W2): เลเยอร์สุดท้ายนี้สร้าง คะแนน สเกลาร์ เดียวสำหรับโหนดแต่ละคู่ ซึ่งแสดงถึง ความน่าจะเป็นที่ขอบ จะมีอยู่ระหว่างโหนดเหล่านั้น


แนวทางแบบแบ่งชั้นนี้ช่วยให้เครื่องทำนายสามารถจับภาพความสัมพันธ์ที่ซับซ้อนระหว่างคู่โหนดและคำนวณคะแนนขอบที่สามารถตีความได้ว่าน่าจะเป็นของการมีอยู่ของขอบนั้น

ฟังก์ชั่นการสูญเสียและ AUC

เพื่อฝึกโมเดลของเราอย่างมีประสิทธิภาพ เราต้องมีฟังก์ชันการสูญเสียที่สามารถ วัดประสิทธิภาพของโมเดล ในการทำนายลิงก์ได้ เนื่องจากงานนี้เป็น ปัญหาการจำแนกแบบไบนารี ซึ่งแต่ละลิงก์มีอยู่หรือไม่มี เราจึงใช้ Binary cross-entropy (BCE) เป็นฟังก์ชันการสูญเสียของเรา Binary cross-entropy วัดความแตกต่างระหว่างคะแนนที่ทำนายของโมเดลกับป้ายกำกับจริง (1 สำหรับลิงก์ที่มีอยู่ 0 สำหรับไม่มีลิงก์) เราใช้เวอร์ชัน _with_logits เนื่องจากโมเดลของเราแสดงคะแนนดิบ (logits) แทนที่จะเป็นความน่าจะเป็น BCE เวอร์ชันนี้มีเสถียรภาพมากกว่าเมื่อทำงานกับ logits เนื่องจากรวมฟังก์ชัน sigmoid และ cross-entropy ไว้ในขั้นตอนเดียว


นี่คือโค้ดที่คำนวณการสูญเสีย:

 def compute_loss(positive_logits, negative_logits): # concatenate positive and negative scores y_predicted = torch.cat([positive_logits, negative_logits]) # create true labels (1 for existing links, 0 for nonexistent) y_true = torch.cat([torch.ones(positive_logits.shape[0]), torch.zeros(negative_logits.shape[0])]) return binary_cross_entropy_with_logits(y_predicted, y_true)


ในการประเมินโมเดล เราใช้เมตริก พื้นที่ใต้เส้นโค้ง ROC (AUC) AUC เหมาะมากสำหรับการคาดการณ์ลิงก์ เนื่องจาก สามารถจัดการข้อมูลที่ไม่สมดุลได้ อย่างมีประสิทธิภาพ โดยตัวอย่างเชิงลบ (ขอบที่ไม่มีอยู่) มักเกิดขึ้นบ่อยกว่าตัวอย่างเชิงบวกมาก คะแนน AUC ช่วยให้เราทราบว่าโมเดลจัดอันดับลิงก์ที่มีอยู่สูงกว่าลิงก์ที่ไม่มีอยู่จริงมากเพียงใด


นี่คือโค้ดสำหรับคำนวณ AUC:

 def compute_auc(positive_logits, negative_logits): y_predicted = torch.cat([positive_logits, negative_logits]).detach().numpy() y_true = torch.cat([torch.ones(positive_logits.shape[0]), torch.zeros(negative_logits.shape[0])]).detach().numpy() return roc_auc_score(y_true, y_predicted)

หมายเหตุ: เราใช้ detach() เพื่อลบเทนเซอร์ออกจากกราฟการคำนวณ ช่วยให้เราสามารถคำนวณ AUC ได้โดยไม่ส่งผลกระทบต่อการไล่ระดับ

การฝึกอบรมโมเดล

ตอนนี้เราพร้อมที่จะฝึกโมเดลแล้ว ในการเริ่มต้น เราจะ สร้างอินสแตนซ์โมเดล ตัวทำนาย และตัวเพิ่มประสิทธิภาพ และกำหนดลูปการฝึก เราจะระบุอัตราการเรียนรู้ ขนาดเลเยอร์ที่ซ่อนอยู่ และอัตราการหลุดออกจากระบบ รวมถึงไฮเปอร์พารามิเตอร์อื่นๆ ด้วย แม้ว่า เราจะไม่ครอบคลุมการเพิ่มประสิทธิภาพไฮเปอร์พารามิเตอร์ ที่นี่ แต่ เราจะใช้ตัวกำหนดตารางอัตราการเรียนรู้ เพื่อปรับอัตราการเรียนรู้หากการสูญเสียถึงจุดคงที่ ซึ่งหมายความว่าอัตราการเรียนรู้จะหยุดลดลงเป็นจำนวนยุคที่กำหนด (ในกรณีนี้คือ 25) เมื่อสิ่งนี้เกิดขึ้น ตัวกำหนดตารางจะลดอัตราการเรียนรู้ลงครึ่งหนึ่ง ซึ่งสามารถช่วยให้โมเดลบรรจบกันได้อย่างมีประสิทธิภาพมากขึ้น


นี่คือโค้ดสำหรับการเริ่มต้นโมเดลและตั้งค่าการฝึกอบรม:

 # init the model num_hidden_features = 32 model = GraphSAGE( train_g.ndata['feat'].shape[1], num_hidden_features, num_hidden_features, ) predictor = MLPPredictor(num_hidden_features) # create an optimizer and a learning rate scheduler learning_rate = 0.01 optimizer = torch.optim.Adam( itertools.chain(model.parameters(), predictor.parameters()), lr=learning_rate, ) lr_scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=25) # train the model num_epochs = 1000 for epoch in range(num_epochs + 1): # forward h = model(train_g, train_g.ndata['feat']) positive_logits = predictor(train_positive_g, h) negative_logits = predictor(train_negative_g, h) loss = compute_loss(positive_logits, negative_logits) # backward optimizer.zero_grad() loss.backward() # clip gradients clip_grad_norm_(model.parameters(), 1.0) optimizer.step() # adjust learning rate based on the loss lr_scheduler.step(loss) # print loss, current learning rate, and AUC if epoch % 100 == 0: last_lr = lr_scheduler.get_last_lr()[0] train_auc = compute_auc(positive_logits, negative_logits) print(f'Epoch: {epoch}, learning rate: {last_lr}, loss: {loss}, AUC: {train_auc}')


มาลองรันโค้ดและตรวจสอบผลลัพธ์กัน:

 Epoch: 0, learning rate: 0.01, loss: 262.4156188964844, AUC: 0.4934097124994463 Epoch: 100, learning rate: 0.01, loss: 0.5642552375793457, AUC: 0.7473735298706314 Epoch: 200, learning rate: 0.01, loss: 0.4622882306575775, AUC: 0.8431058751115716 Epoch: 300, learning rate: 0.01, loss: 0.40566185116767883, AUC: 0.8777374138645864 Epoch: 400, learning rate: 0.01, loss: 0.38118976354599, AUC: 0.8944719038039551 Epoch: 500, learning rate: 0.01, loss: 0.3690297603607178, AUC: 0.9039401673234729 Epoch: 600, learning rate: 0.005, loss: 0.3579995930194855, AUC: 0.9112366798940639 Epoch: 700, learning rate: 0.005, loss: 0.3557407557964325, AUC: 0.9128097572016495 Epoch: 800, learning rate: 0.005, loss: 0.3510144352912903, AUC: 0.9152937255697913 Epoch: 900, learning rate: 0.00125, loss: 0.3425179123878479, AUC: 0.9202487786553115 Epoch: 1000, learning rate: 0.00015625, loss: 0.3432360589504242, AUC: 0.9198250134354529


ดังที่เราเห็น โมเดลนี้บรรลุ AUC ประมาณ 0.92 ซึ่งบ่งชี้ถึงประสิทธิภาพการทำนายที่แข็งแกร่ง สังเกตว่าอัตราการเรียนรู้จะลดลงระหว่างยุค 500 ถึง 600 เมื่อการสูญเสียคงที่ การปรับเปลี่ยนนี้ช่วยให้ปรับแต่งได้ละเอียดขึ้น ส่งผลให้การสูญเสียลดลงเล็กน้อย หลังจากจุดหนึ่ง การสูญเสียและ AUC จะคงที่ ซึ่งบ่งชี้ว่าโมเดลได้ บรรจบกันแล้ว


มา ประเมินโมเดลจากข้อมูลทดสอบ (ซึ่งไม่ได้ใช้ระหว่างการฝึก) และดูว่าสามารถสรุปผลได้ดีหรือไม่:

 # evaluate the model on the test data with torch.no_grad(): test_positive_scores = predictor(test_positive_g, h) test_negative_scores = predictor(test_negative_g, h) test_loss = compute_loss(test_positive_scores, test_negative_scores) test_auc = compute_auc(test_positive_scores, test_negative_scores) print(f'Test loss: {test_loss}, Test AUC: {test_auc}')


ผลลัพธ์ที่ได้คือ:

 Test loss: 0.675215482711792, Test AUC: 0.866213400711374


ค่า AUC ของการทดสอบนั้นต่ำกว่าค่า AUC ของการฝึกเล็กน้อย ซึ่งบ่งชี้ถึงการโอเวอร์ฟิตติ้งเล็กน้อย อย่างไรก็ตาม ค่า AUC ที่ 0.866 แสดงให้เห็นว่าแบบจำลองยังคงทำงานได้ดีกับข้อมูลที่มองไม่เห็น การปรับไฮเปอร์พารามิเตอร์เพิ่มเติมอาจช่วยปรับปรุงการสรุปทั่วไปได้ โดยเฉพาะอย่างยิ่งหากการโอเวอร์ฟิตติ้งเป็นปัญหา

การใช้โมเดลที่ผ่านการฝึกอบรมเพื่อคาดการณ์ลิงก์ใหม่

ด้วยโมเดลที่ผ่านการฝึกอบรมของเรา ตอนนี้เราสามารถคาดการณ์ความน่าจะเป็นของการเชื่อมโยง ระหว่างโหนดในกราฟได้แล้ว เราจะสร้าง การคาดการณ์สำหรับคู่โหนดที่เป็นไปได้ทั้งหมด ซึ่งช่วยให้เราสามารถระบุการเชื่อมต่อใหม่ที่อาจเกิดขึ้นได้

  1. การสร้างคู่โหนดตัวเลือก : ก่อนอื่น เราจะสร้างคู่โหนดที่เป็นไปได้ทั้งหมด ยกเว้นการเชื่อมต่อแบบ self-loop (การเชื่อมต่อจากโหนดไปยังโหนดเอง) ซึ่งจะทำให้เราได้คู่โหนดตัวเลือกซึ่งเราต้องการทำนายความน่าจะเป็นของลิงก์
  2. การสร้างกราฟผู้สมัคร: จากนั้นเราจะสร้างกราฟ DGL ที่มีขอบผู้สมัครเหล่านี้ และใช้โมเดลที่มีการฝังโหนดจากกราฟต้นฉบับ การฝังเหล่านี้ซึ่งจัดเก็บในแอตทริบิวต์ ndata['h'] ของกราฟผู้สมัคร จะทำหน้าที่เป็นอินพุตสำหรับการทำนายลิงก์


นี่คือโค้ดสำหรับขั้นตอนเหล่านี้:

 # build node pairs, avoid self-loops (with_replacement=False) node_pairs = torch.combinations(torch.arange(g.num_nodes()), r=2, with_replacement=False) candidate_u = node_pairs[:, 0] candidate_v = node_pairs[:, 1] # build a graph with all node pairs candidate_graph = dgl.graph((candidate_u, candidate_v)) candidate_graph_node_embeddings = model(g, g.ndata['feat']) # we use embeddings from the original graph candidate_graph.ndata['h'] = candidate_graph_node_embeddings # use the predictor to predict the existence of links between nodes predicted_scores = predictor(candidate_graph, candidate_graph_node_embeddings)


ตอนนี้ เรามีการคาดการณ์สำหรับคู่ผู้สมัครทั้งหมดแล้ว เราสามารถตรวจสอบความน่าจะเป็นของการเชื่อมโยงระหว่างโหนดเฉพาะใดๆ ได้ ตัวอย่างเช่น ลองตรวจสอบคะแนนและความน่าจะเป็นของการเชื่อมโยงระหว่างโหนด 1773 และ 7005 ซึ่งไม่ได้เชื่อมต่อโดยตรงในชุดข้อมูลเริ่มต้น:

 # find the index of the node pair (1773, 7005) pair_index = torch.where((candidate_u == 1773) & (candidate_v == 7005))[0] print(f'Pair index: {pair_index}') # get the logit score for this pair and compute probability of link existence pair_link_score = predicted_scores[pair_index].item() # logit score print(f'Pair link score: {pair_link_score}') link_probability = torch.sigmoid(torch.tensor(pair_link_score)).item() # apply sigmoid to convert score into probability print(f'Link probability: {link_probability * 100}%')


ผลลัพธ์ที่ได้คือ:

 Pair index: tensor([11066978]) Pair link score: 0.7675977945327759 Link probability: 68.30010414123535%


ตามแบบจำลองของเรา มี ความน่าจะเป็น 68.3% ที่จะมีลิงก์ระหว่างผู้ใช้ 1773 และ 7005

บทสรุป

ในโพสต์นี้ เราได้สร้างแบบจำลองสำหรับทำนายลิงก์ใหม่ในกราฟโซเชียลสำเร็จแล้ว โดยสาธิตการใช้ Graph Neural Networks และ DGL สำหรับทำนายลิงก์ การใช้ชุดข้อมูลที่ค่อนข้างเล็กทำให้เราทำงานบนเครื่องภายในได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม เนื่องจากกราฟขยายขนาดเป็นโหนดและขอบจำนวนล้านหรือพันล้านโหนด การจัดการกับโหนดและขอบเหล่านี้จึงต้องใช้โซลูชันขั้นสูง เช่น การฝึกอบรมแบบกระจายบนคลัสเตอร์ GPU


ในขั้นตอนถัดไป เราจะสำรวจแนวทางในการจัดการกราฟขนาดใหญ่และการนำการคาดการณ์ลิงก์ไปใช้ในสภาพแวดล้อมคลาวด์ ซึ่งทำให้คุณสามารถประยุกต์ใช้เทคนิคเหล่านี้กับชุดข้อมูลระดับการผลิตได้