paint-brush
ទស្សន៍ទាយតំណភ្ជាប់ក្នុងក្រាហ្វជាមួយបណ្តាញសរសៃប្រសាទក្រាហ្វ និង DGL.aiដោយ@andrei9735
366 ការអាន
366 ការអាន

ទស្សន៍ទាយតំណភ្ជាប់ក្នុងក្រាហ្វជាមួយបណ្តាញសរសៃប្រសាទក្រាហ្វ និង DGL.ai

ដោយ Andrei14m2024/11/06
Read on Terminal Reader

យូរ​ពេក; អាន

រៀនបង្កើតគំរូសម្រាប់ការទស្សន៍ទាយតំណថ្មីនៅក្នុងក្រាហ្វសង្គម បង្ហាញពីការប្រើប្រាស់បណ្តាញសរសៃប្រសាទក្រាហ្វ និង DGL សម្រាប់ការទស្សន៍ទាយតំណ។
featured image - ទស្សន៍ទាយតំណភ្ជាប់ក្នុងក្រាហ្វជាមួយបណ្តាញសរសៃប្រសាទក្រាហ្វ និង DGL.ai
Andrei HackerNoon profile picture


ក្រាហ្វគឺជាមធ្យោបាយដ៏មានអានុភាពមួយដើម្បីតំណាងឱ្យទិន្នន័យ ដោយចាប់យកទំនាក់ទំនងរវាងអង្គភាពនានាតាមកម្មវិធីជាច្រើន។ មិនថាអ្នកកំពុងយកគំរូតាមបណ្តាញសង្គម អន្តរកម្មប្រូតេអ៊ីន ប្រព័ន្ធដឹកជញ្ជូន ឬម៉ាស៊ីនណែនាំទេ ក្រាហ្វបង្ហាញពីធម្មជាតិ និងវិភាគភាពអាស្រ័យគ្នាទៅវិញទៅមកដ៏ស្មុគស្មាញទាំងនេះ។ នៅក្នុងពិភពដែលជំរុញដោយទិន្នន័យនាពេលបច្ចុប្បន្ននេះ ការយល់ដឹងអំពីទំនាក់ទំនងរវាងអង្គភាពគឺមានសារៈសំខាន់ដូចគ្នានឹងការយល់ដឹងអំពីអង្គភាពខ្លួនឯងដែរ - នេះគឺជាកន្លែងដែលក្រាហ្វពិតជាភ្លឺច្បាស់។


ការទស្សន៍ទាយតំណ គឺជាកិច្ចការមូលដ្ឋានមួយក្នុងការវិភាគក្រាហ្វ ដែលពាក់ព័ន្ធនឹងការព្យាករណ៍នៃការតភ្ជាប់ (ឬតំណភ្ជាប់) រវាងថ្នាំង (ធាតុដែលតំណាងនៅក្នុងក្រាហ្វ)។ ស្រមៃថាការណែនាំមិត្តភក្តិថ្មីនៅក្នុងបណ្តាញសង្គម ទស្សន៍ទាយការសហការដ៏មានសក្តានុពលនៅក្នុងក្រាហ្វការដកស្រង់ការសិក្សា ឬការព្យាករណ៍អន្តរកម្មនាពេលអនាគតរវាងអ្នកប្រើប្រាស់ និងផលិតផលនៅក្នុងការកំណត់ពាណិជ្ជកម្មអេឡិចត្រូនិក - ទាំងនេះគឺជាឧទាហរណ៍ទាំងអស់នៃការទស្សន៍ទាយតំណនៅក្នុងសកម្មភាព។ កិច្ចការនេះជួយពង្រីកបណ្តាញ សន្និដ្ឋានព័ត៌មានដែលបាត់ និងរកឃើញភាពមិនប្រក្រតី។ សម្រាប់កម្មវិធីដែលរាប់ចាប់ពីការបង្កើនបទពិសោធន៍អ្នកប្រើប្រាស់ ដល់ការកែលម្អការរកឃើញការក្លែងបន្លំ ការព្យាករណ៍តំណគឺជាធាតុផ្សំដ៏សំខាន់សម្រាប់ភាពជោគជ័យ។


ដើម្បីបង្ហាញពីការទស្សន៍ទាយតំណ យើងនឹងប្រើ សំណុំទិន្នន័យបណ្តាញសង្គម Twitch ពីគម្រោងវិភាគបណ្តាញ Stanford (SNAP)។ សំណុំទិន្នន័យនេះចាប់យកទំនាក់ទំនងសង្គមរវាងអ្នកប្រើប្រាស់នៅលើវេទិកាស្ទ្រីម Twitch ដែលថ្នាំងតំណាងឱ្យអ្នកប្រើប្រាស់ Twitch ហើយគែមតំណាងឱ្យមិត្តភាពរវាងពួកគេ។ សំណុំទិន្នន័យមានរចនាសម្ព័ន្ធល្អ ធ្វើឱ្យវាងាយស្រួលក្នុងការដំណើរការ និងធ្វើការជាមួយ។


តាមរយៈការធ្វើតាម អ្នកនឹងរៀនពីរបៀបរៀបចំគម្រោង ទិន្នន័យមុនដំណើរការ បង្កើតគំរូ និងវាយតម្លៃវាសម្រាប់ការទស្សន៍ទាយតំណនៅលើសំណុំទិន្នន័យពិភពពិត។

Graph Neural Networks និងបណ្ណាល័យ Deep Graph

ការធ្វើការជាមួយទិន្នន័យដែលមានរចនាសម្ព័ន្ធក្រាហ្វបង្ហាញពីបញ្ហាប្រឈមតែមួយគត់ ហើយនេះគឺជាកន្លែងដែល បណ្តាញសរសៃប្រសាទក្រាហ្វ (GNNs) ចូលមក។ GNNs គឺជាប្រភេទនៃបណ្តាញសរសៃប្រសាទដែលត្រូវបានរចនាឡើងជាពិសេសដើម្បីធ្វើការជាមួយទិន្នន័យក្រាហ្វ។ មិនដូចបណ្តាញសរសៃប្រសាទប្រពៃណីដែលដំណើរការលើការបញ្ចូលទំហំថេរ GNNs អាចគ្រប់គ្រងទំហំក្រាហ្វតាមអំពើចិត្ត និងប្រើប្រាស់លំនាំនៃការតភ្ជាប់នៅក្នុងទិន្នន័យ។ ដោយការប្រមូលផ្តុំព័ត៌មានពីអ្នកជិតខាងរបស់ថ្នាំង GNNs រៀនតំណាងដែលចាប់យកទាំងគុណលក្ខណៈថ្នាំង និងរចនាសម្ព័ន្ធនៃក្រាហ្វ ដែលធ្វើឱ្យវាមានប្រសិទ្ធភាពខ្ពស់សម្រាប់កិច្ចការដូចជា ការចាត់ថ្នាក់ថ្នាំង ការព្យាករណ៍តំណ និងការចាត់ថ្នាក់ក្រាហ្វ។


បណ្ណាល័យ Deep Graph ( DGL.ai ) គឺជាកញ្ចប់ឧបករណ៍ដ៏មានអានុភាពសម្រាប់បង្កើត GNNs ដោយភាពងាយស្រួល និងប្រសិទ្ធភាព។ ជាមួយនឹង DGL អ្នកអភិវឌ្ឍន៍អាចប្រើប្រាស់ស្ថាបត្យកម្ម GNN ទំនើបបំផុត ដើម្បីដោះស្រាយកិច្ចការជាច្រើន រួមទាំងការព្យាករណ៍តំណ។ DGL ផ្ដល់នូវឧបករណ៍ប្រើប្រាស់ជាច្រើនសម្រាប់ធ្វើការជាមួយក្រាហ្វដែលមានលក្ខណៈដូចគ្នា និងតំណពូជ ដែលធ្វើឱ្យវាក្លាយជាឧបករណ៍ដែលអាចប្រើប្រាស់បានសម្រាប់អ្នកស្រាវជ្រាវ និងអ្នកអនុវត្តដូចគ្នា។ តាមរយៈការធ្វើឱ្យការអនុវត្ត GNNs មានភាពសាមញ្ញ DGL អនុញ្ញាតឱ្យអ្នកផ្តោតការយកចិត្តទុកដាក់បន្ថែមទៀតលើការបង្កើតដំណោះស្រាយប្រកបដោយភាពច្នៃប្រឌិត ជាជាងធ្វើឱ្យមានការភ័ន្តច្រឡំនៅក្នុងភាពស្មុគស្មាញផ្នែកបច្ចេកទេស។


ជាមួយនឹងមូលដ្ឋានគ្រឹះនេះ ចូរយើងចូលទៅក្នុងការកសាងគំរូព្យាករណ៍តំណដោយប្រើ GNNs និង 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. កំពុងផ្ទុកមុខងារ Node ៖ បន្ទាប់មក យើងផ្ទុកមុខងារ node ពីឯកសារ 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

យើងនឹងប្រើ Graph Sample and Aggregate (GraphSAGE) convolutional neural network ដើម្បីស្វែងយល់ពីតំណាង node ដែលគេស្គាល់ផងដែរថាជា embeddings ដែលចាប់យកទាំងរចនាសម្ព័ន្ធ និងលក្ខណៈនៃ node នីមួយៗនៅក្នុងក្រាហ្វ។ GraphSAGE ដំណើរការដោយការប្រមូលផ្តុំព័ត៌មានលក្ខណៈពិសេសពីអ្នកជិតខាងរបស់ថ្នាំងនីមួយៗដើម្បីបង្កើតតំណាងដ៏មានអត្ថន័យសម្រាប់ថ្នាំងនីមួយៗ។ ដំណើរការនេះ ត្រូវបានគេស្គាល់ថាជា ការប្រមូលផ្តុំអ្នកជិតខាង អនុញ្ញាតឱ្យគំរូដើម្បីស្វែងយល់ពីគំរូដែលបានធ្វើមូលដ្ឋានីយកម្មសម្បូរបែបនៅក្នុងក្រាហ្វ។


នៅក្នុងស្រទាប់ GraphSAGE នីមួយៗ គំរូអនុវត្ត មុខងារប្រមូលផ្តុំមួយ (ក្នុងករណីនេះ មុខងារ "មធ្យម") ដើម្បីប្រមូលព័ត៌មានពីថ្នាំងជិតខាង ដែលបន្ទាប់មកត្រូវបានផ្សំជាមួយនឹងលក្ខណៈផ្ទាល់ខ្លួនរបស់ថ្នាំង។ ការដាក់ជង់ ស្រទាប់ convolutional ច្រើន អនុញ្ញាតឱ្យគំរូ ចាប់យកព័ត៌មានពីថ្នាំងឆ្ងាយកាន់តែខ្លាំង ពង្រីកទិដ្ឋភាពរបស់ថ្នាំងនីមួយៗក្នុងក្រាហ្វប្រកបដោយប្រសិទ្ធភាព។


ដើម្បីបង្កើនប្រសិទ្ធភាពនៃម៉ូដែល និងកាត់បន្ថយការពាក់លើស យើងនឹងអនុវត្ត ការបោះបង់ បន្ទាប់ពីស្រទាប់នីមួយៗ។


ឥឡូវនេះ ចូរយើងបង្កើតគំរូ GraphSAGE ជាមួយនឹង ស្រទាប់បី convolutional រួមជាមួយនឹងមុខងារ 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 ) មានការបង្កប់ថ្នាំង។ ដើម្បីទស្សន៍ទាយលទ្ធភាពនៃគែម (ឬតំណភ្ជាប់) រវាងថ្នាំងទាំងពីរណាមួយ យើងនឹងប្រើ Multi-Layer Perceptron (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-entroy វាស់ភាពខុសគ្នារវាងពិន្ទុដែលបានព្យាករណ៍របស់ម៉ូដែល និងស្លាកពិតប្រាកដ (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 Curve (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() ដើម្បីដក tensors ចេញពីក្រាហ្វគណនា ដែលអនុញ្ញាតឱ្យយើងគណនា 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. ការបង្កើតគូថ្នាំងបេក្ខជន ៖ ដំបូងយើងបង្កើតគូថ្នាំងដែលអាចធ្វើបានទាំងអស់ ដោយមិនរាប់បញ្ចូលរង្វិលជុំដោយខ្លួនឯង (ការតភ្ជាប់ពីថ្នាំងទៅខ្លួនវា)។ វាផ្តល់ឱ្យយើងនូវគូថ្នាំងបេក្ខជនដែលយើងចង់ទស្សន៍ទាយប្រូបាប៊ីលីតេនៃតំណភ្ជាប់។
  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

សេចក្តីសន្និដ្ឋាន

នៅក្នុងការបង្ហោះនេះ យើងបានបង្កើតគំរូដោយជោគជ័យសម្រាប់ការទស្សន៍ទាយតំណថ្មី នៅក្នុងក្រាហ្វសង្គម ដោយបង្ហាញពីការប្រើប្រាស់បណ្តាញសរសៃប្រសាទក្រាហ្វ និង DGL សម្រាប់ការទស្សន៍ទាយតំណ។ ការប្រើប្រាស់សំណុំទិន្នន័យតូចមួយបានអនុញ្ញាតឱ្យយើងធ្វើការប្រកបដោយប្រសិទ្ធភាពនៅលើម៉ាស៊ីនក្នុងស្រុក។ ទោះយ៉ាងណាក៏ដោយ ដោយសារក្រាហ្វមានទំហំរហូតដល់រាប់លាន ឬរាប់ពាន់លាននៃថ្នាំង និងគែម ការគ្រប់គ្រងពួកវាទាមទារដំណោះស្រាយកម្រិតខ្ពស់បន្ថែមទៀត ដូចជាការបណ្តុះបណ្តាលចែកចាយនៅលើចង្កោម GPU ជាដើម។


ជាជំហានបន្ទាប់ យើងនឹងស្វែងរកវិធីសាស្រ្តសម្រាប់ដោះស្រាយក្រាហ្វខ្នាតធំ និងអនុវត្តការព្យាករណ៍តំណនៅក្នុងបរិស្ថានពពក ដែលអនុញ្ញាតឱ្យអ្នកអនុវត្តបច្ចេកទេសទាំងនេះទៅនឹងសំណុំទិន្នន័យកម្រិតផលិតកម្ម។

L O A D I N G
. . . comments & more!

About Author

Andrei HackerNoon profile picture
Software Engineer, Cloud Solutions Architect

ព្យួរស្លាក

អត្ថបទនេះត្រូវបានបង្ហាញនៅក្នុង...