Graph attention network (GAT) for node classification

Graph attention network (GAT) for node classification

Graph Attention Networks:

graph neural network  یک کلاس از شبکه‌های عصبی است که برای پردازش داده‌های گراف مانند گراف‌های رسانه‌های اجتماعی و گراف‌های مربوط به پروتئین‌ها و مولکول‌های مختلف استفاده می‌شود. Graph attention network  یک معماری نوین از شبکه عصبی است که با استفاده از لایه‌های خودتوجه با ماسک، بر روی داده‌های ساختاری گراف عمل می‌کند و مشکلات کانولوشن گراف یا تقریب آن را حل می‌کند. در این شبکه، لایه‌ها به صورت پشته‌ای قرار می‌گیرند به طوری که گره‌ها می‌توانند بر روی ویژگی‌های همسایگان خود توجه کنند . برای دسته‌بندی گره، معمولاً از شبکه‌های عصبی گرافی استفاده می‌شود. این شبکه‌ها قادر به یادگیری و تعمیم الگوهای موجود در ویژگی‌های گره‌ها و رابطه‌های بینشان هستند. برای این کار، معمولاً از لایه‌های پرسپترون، لایه‌های خودتوجه، و لایه‌های توجه چند سر استفاده می‌شود. با استفاده از شبکه‌های عصبی گرافی در دسته‌بندی گره، می‌توانیم الگوریتم‌های قوی و انعطاف‌پذیری برای تحلیل و پیش‌بینی ویژگی‌ها و خصوصیات گره‌ها در گراف‌ها به دست آوریم. شبکه توجه گراف (GAT) نوعی معماری شبکه عصبی است که به طور خاص برای وظایف طبقه بندی گره ها در داده های ساختار یافته گراف طراحی شده است. این معماری در سال 2018 توسط Veličković و همکارانش معرفی شد به عنوان یک روش برای مدلسازی موثر روابط بین گره ها در یک گراف و درک اهمیت گره های مختلف برای طبقه بندی. در شبکه های عصبی گراف سنتی، اطلاعات از گره های همسایه به طور معمول با میانگین ساده یا مجموع وزن دار ویژگی های آنها تجمیع می شود. با این حال، GAT مکانیزم های توجه را به کار می گیرد تا به طور پویا وزن های مختلفی به همسایگان مختلف اختصاص دهد، که به شبکه اجازه می دهد در فرآیند تجمیع بیشتر بر روی گره های مرتبط تمرکز کند. در ادامه توضیح مفصلی از معماری GAT و اجزای کلیدی آن آورده شده است:

۱. نمایش ورودی:

  • هر گره در گراف با یک بردار ویژگی که ویژگی ها یا خصوصیات آن را نشان می دهد، مرتبط است.
  • در GAT، این بردارهای ویژگی به طور معمول به عنوان یک ماتریس X به شکل (N، F) نمایش داده می شوند، که در آن N تعداد گره ها و F تعداد ویژگی های ورودی برای هر گره است.

۲. مکانیزم توجه:

  • GAT از مکانیزم های توجه برای محاسبه ضرایب توجه استفاده می کند که اهمیت گره های مختلف را ثبت می کنند.
  • ضرایب توجه با مقایسه نمایش ویژگی یک گره مرکزی با گره های همسایه آن محاسبه می شوند.
  • GAT از یک مکانیزم توجه خودمانند استفاده می کند، جایی که نمایش ویژگی یک گره با نمایش ویژگی های همسایگان خود مقایسه می شود.

۳. ضرایب توجه:

  • ضرایب توجه اهمیت یا ارتباط هر گره همسایه را برای گره مرکزی مشخص می کنند.
  • برای محاسبه ضرایب توجه، GAT از یک فرشبکه توجه گراف (GAT) نوعی معماری شبکه عصبی است که به طور خاص برای وظایف طبقه‌بندی گره‌ها در داده‌های ساختاری گراف طراحی شده است. این معماری در سال 2018 توسط ولیچکوویچ و همکارانش به عنوان یک روش برای مدلسازی موثر روابط بین گره‌ها در یک گراف و درک اهمیت گره‌های مختلف برای طبقه‌بندی معرفی شد.

۴.جمع آوری :

  •  پس از محاسبه ضرایب توجه، GAT اطلاعات از همسایگان را با استفاده از این ضرایب جمع‌آوری می‌کند، که منجر به نمایش بهبود یافته برای هر گره می‌شود.

۵.خروجی:

  • در نهایت، نمایش‌های جمع‌آوری شده از گره‌ها برای طبقه‌بندی گره‌ها استفاده می‌شود، معمولاً از طریق یک لایه softmax که برچسب کلاس را به هر گره اختصاص می‌دهد.
 

Import packages:

import tensorflow as tf

from tensorflow import keras

from tensorflow.keras import layers

import numpy as np

import pandas as pd

import os

import warnings

warnings.filterwarnings("ignore")

pd.set_option("display.max_columns", 6)

pd.set_option("display.max_rows", 6)

np.random.seed(2)

Obtain the dataset:

آماده‌سازی مجموعه داده Cora به همان روش آماده‌سازی مجموعه داده دسته‌بندی گره با شبکه‌های عصبی گرافیکی (Graph Neural Networks) انجام می‌شود. به طور خلاصه، مجموعه داده Cora شامل دو فایل است: فایل cora.cites   و فایل cora.content  است. در این کد، ابتدا از تابع `get_file` در ماژول `keras.utils` استفاده شده است تا فایل مجموعه داده Cora را از آدرس مشخص شده در `origin` دریافت کند و آن را در مسیر `zip_file` ذخیره کند. سپس با استفاده از تابع `os.path.join`، مسیر پوشه `cora` را در مسیر پوشه حاوی فایل فشرده مجموعه داده تشکیل می‌دهد. سپس، دو فایل CSV موجود در مجموعه داده را به کمک تابع `pd.read_csv` در ماژول `pandas` بارگیری می‌کند. فایل `cora.cites` که شامل ارتباطات جهت‌دار بین مقالات است، با استفاده از آرگومان‌های `sep="\t"` و `names=["target", "source"]` خوانده می‌شود. همچنین، فایل `cora.content` که شامل ویژگی‌ها و برچسب‌های مقالات است، با استفاده از آرگومان‌های `sep="\t"` و `names=["paper_id"] + [f"term_{idx}" for idx in range(1433)] + ["subject"]` خوانده می‌شود. سپس، مقادیر منحصر به فرد موجود در ستون `subject` (برچسب‌ها) مرتب می‌شوند و در `class_values` ذخیره می‌شوند. همچنین، یک دیکشنری به نام `class_idx` تشکیل داده می‌شود که هر برچسب را به یک شناسه مرتبط می‌دهد. همچنین، یک دیکشنری دیگر به نام `paper_idx` تشکیل داده می‌شود که هر شناسه مقاله را به یک شناسه عددی مرتبط می‌دهد. در ادامه، با استفاده از توابع `apply`، ستون `paper_id` در جدول `papers` به شناسه مرتبط در دیکشنری `paper_idx` تبدیل می‌شود. همچنین، ستون‌های `source` و `target` در جدول `citations` به شناسه‌های مرتبط در دیکشنری `paper_idx` تبدیل می‌شوند. همچنین، ستون `subject` در جدول `papers` به شناسه مرتبط در دیکشنری `class_idx` تبدیل می‌شود. در نهایت، جداول `citations` و `papers` چاپ می‌شوند تا محتوای آن‌ها را مشاهده کنید.
zip_file = keras.utils.get_file(

fname="cora.tgz",

origin="https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz",

extract=True,

)




data_dir = os.path.join(os.path.dirname(zip_file), "cora")




citations = pd.read_csv(

os.path.join(data_dir, "cora.cites"),

sep="\t",

header=None,

names=["target", "source"],

)

papers = pd.read_csv(

os.path.join(data_dir, "cora.content"),

sep="\t",

header=None,

names=["paper_id"] + [f"term_{idx}" for idx in range(1433)] + ["subject"],

)




class_values = sorted(papers["subject"].unique())

class_idx = {name: id for id, name in enumerate(class_values)}

paper_idx = {name: idx for idx, name in enumerate(sorted(papers["paper_id"].unique()))}

papers["paper_id"] = papers["paper_id"].apply(lambda name: paper_idx[name])

citations["source"] = citations["source"].apply(lambda name: paper_idx[name])

citations["target"] = citations["target"].apply(lambda name: paper_idx[name])

papers["subject"] = papers["subject"].apply(lambda value: class_idx[value])




print(citations)

print(papers)
 

Split the dataset:

در این کد، ابتدا با استفاده از تابع np.random.permutation از ماژول numpy، اندیس‌های تصادفی برای دسترسی به ردیف‌های جدول papers تولید می‌شود. این اندیس‌ها ترتیب تصادفی را در رنج اعداد صفر تا تعداد ردیف‌های جدول papers دارند. سپس، با استفاده از این اندیس‌ها، مجموعه داده به دو بخش تقسیم می‌شود. بخش اول که به آن train_data نامیده شده است، شامل نیمی از ردیف‌های جدول papers است که اندیس آن‌ها از ابتدای لیست تصادفی تولید شده است. بخش دوم که به آن test_data نامیده شده است، شامل نیمی دیگر از ردیف‌های جدول papers است که اندیس آن‌ها از نصف تا انتهای لیست تصادفی تولید شده است. با این تقسیم، مجموعه داده به دو بخش آموزش و آزمون تقسیم شده است. این تقسیم معمولاً با نسبت 50/50 صورت می‌گیرد، اما می‌توان نسبت دیگری را نیز انتخاب کرد.
# Obtain random indices

random_indices = np.random.permutation(range(papers.shape[0]))


# 50/50 split

train_data = papers.iloc[random_indices[: len(random_indices) // 2]]

test_data = papers.iloc[random_indices[len(random_indices) // 2 :]]

Prepare the graph data:

در این کد، ابتدا اندیس‌های papers که در زمان آموزش مدل برای جمع‌آوری وضعیت نودها از گراف استفاده می‌شود، از جدول train_data و test_data استخراج می‌شود. این اندیس‌ها در ستون "paper_id" وجود دارند و با استفاده از تابع to_numpy() به آرایه نامپای تبدیل می‌شوند. این آرایه‌ها به ترتیب در متغیرهای train_indices و test_indices ذخیره می‌شوند. سپس، برچسب‌های واقعی متناظر با هر paper(براساس شناسه داده) از جدول train_data و test_data استخراج می‌شوند. این برچسب‌ها در ستون "subject" قرار دارند و با استفاده از تابع to_numpy() به آرایه نامپای تبدیل می‌شوند. این آرایه‌ها به ترتیب در متغیرهای train_labels و test_labels ذخیره می‌شوند. سپس، گراف تعریف می‌شود. یال‌های گراف در متغیر edges قرار داده می‌شوند که با استفاده از تابع tf.convert_to_tensor از جدول citations استخراج می‌شوند. این جدول دارای دو ستون "target" و "source" است که نشان‌دهنده اندیس‌های داده هایی هستند که یال بین آن‌ها وجود دارد. ویژگی‌های نودها در متغیر node_states قرار داده می‌شوند. این متغیر با استفاده از تابع tf.convert_to_tensor از جدول papers استخراج می‌شود. جدول papers شامل ویژگی‌هایpapers است که در ستون‌های بعد از ستون "paper_id" و قبل از ستون نشان‌دهنده برچسب "subject" قرار دارند.
# Obtain paper indices which will be used to gather node states

# from the graph later on when training the model

train_indices = train_data["paper_id"].to_numpy()

test_indices = test_data["paper_id"].to_numpy()


# Obtain ground truth labels corresponding to each paper_id

train_labels = train_data["subject"].to_numpy()

test_labels = test_data["subject"].to_numpy()


# Define graph, namely an edge tensor and a node feature tensor

edges = tf.convert_to_tensor(citations[["target", "source"]])

node_states = tf.convert_to_tensor(papers.sort_values("paper_id").iloc[:, 1:-1])


# Print shapes of the graph

print("Edges shape:\t\t", edges.shape)

print("Node features shape:", node_states.shape)
خروجی: در انتها، با استفاده از تابع print، ابعاد ماتریس‌های edges و node_states چاپ می‌شوند. این ابعاد به ترتیب در خروجی نشان داده شده‌اند. ابعاد edges برابر با (5429, 2) است که نشان‌دهنده تعداد یال‌ها و ستون‌هایی که از هر یال استخراج شده‌اند است. ابعاد node_states برابر با (2708, 1433) است که نشان‌دهنده تعداد نودها و تعداد ویژگی‌های هر نود است.  

Build the model:

GAT یک گراف را (یعنی یک تانسور یال و یک تانسور ویژگی نود) به عنوان ورودی دریافت می‌کند و وضعیت نودها را به روزرسانی می‌کند. وضعیت نودها برای هر نود هدف، اطلاعات تجمعی محلی از N پرش‌ها (که تعداد لایه‌های GAT تعیین می‌کند) است. در مقابل، به طور مهم، GAT برخلاف شبکه همبندی گراف (GCN) از مکانیسم‌های توجه برای تجمیع اطلاعات از نودهای همسایه (یا نودهای مبدا) استفاده می‌کند. به عبارت دیگر، به جای متوسط ​​/ جمع وضعیت نودهای مبدا (مقاله‌های مبدا) به نود هدف (مقاله‌های هدف)، GAT ابتدا امتیازهای توجه نرمال شده را به هر وضعیت نود مبدا اعمال کرده و سپس جمع می‌کند. مدل GAT لایه‌های توجه گراف چند سر را پیاده‌سازی می‌کند. لایه MultiHeadGraphAttention به سادگی یک اتصال (یا میانگین) از چند لایه توجه گراف (GraphAttention) با وزن‌های جداگانه قابل یادگیری W است. لایه GraphAttention عملکرد زیر را دارد:

(Multi-head) graph attention layer:

ابتدا وضعیت نود ورودی h^{l} که توسط W^{l} خطی تبدیل می‌شود، به z^{l} منتقل می‌شود. برای هر نود هدف: امتیازهای توجه دو به دو a^{l}^{T}(z^{l}{i}||z^{l}{j}) برای همه j محاسبه می‌شود که به e_{ij} منجر می‌شود (برای همه j). علامت || نشان‌دهنده اتصال است، {i} به نود هدف اشاره دارد و {j} به نود همسایه/مبدا یک پرش داده می‌شود. مقادیر e{ij} از طریق softmax نرمال شده و از این رو مجموع امتیازهای توجه یال‌های ورودی به نود هدف (sum{k}{e_{norm}{ik}}) برابر با 1 خواهد بود. امتیازهای توجه e{norm}{ij} را به z{j} اعمال کرده و به وضعیت جدید نود هدف h^{l+1}_{i} اضافه می‌کند، برای همه j.
class GraphAttention(layers.Layer):

    def __init__(

        self,

        units,

        kernel_initializer="glorot_uniform",

        kernel_regularizer=None,

        **kwargs,

    ):

        super().__init__(**kwargs)

        self.units = units

        self.kernel_initializer = keras.initializers.get(kernel_initializer)

        self.kernel_regularizer = keras.regularizers.get(kernel_regularizer)

    def build(self, input_shape):

        self.kernel = self.add_weight(

            shape=(input_shape[0][-1], self.units),

            trainable=True,

            initializer=self.kernel_initializer,

            regularizer=self.kernel_regularizer,

            name="kernel",

        )

        self.kernel_attention = self.add_weight(

            shape=(self.units * 2, 1),

            trainable=True,

            initializer=self.kernel_initializer,

            regularizer=self.kernel_regularizer,

            name="kernel_attention",

        )

        self.built = True

    def call(self, inputs):

        node_states, edges = inputs

        # Linearly transform node states

        node_states_transformed = tf.matmul(node_states, self.kernel)

        # (1) Compute pair-wise attention scores

        node_states_expanded = tf.gather(node_states_transformed, edges)

        node_states_expanded = tf.reshape(

            node_states_expanded, (tf.shape(edges)[0], -1)

        )

        attention_scores = tf.nn.leaky_relu(

            tf.matmul(node_states_expanded, self.kernel_attention)

        )

        attention_scores = tf.squeeze(attention_scores, -1)

        # (2) Normalize attention scores

        attention_scores = tf.math.exp(tf.clip_by_value(attention_scores, -2, 2))

        attention_scores_sum = tf.math.unsorted_segment_sum(

            data=attention_scores,

            segment_ids=edges[:, 0],

            num_segments=tf.reduce_max(edges[:, 0]) + 1,

        )

        attention_scores_sum = tf.repeat(

            attention_scores_sum, tf.math.bincount(tf.cast(edges[:, 0], "int32"))

        )

        attention_scores_norm = attention_scores / attention_scores_sum

        # (3) Gather node states of neighbors, apply attention scores and aggregate

        node_states_neighbors = tf.gather(node_states_transformed, edges[:, 1])

        out = tf.math.unsorted_segment_sum(

            data=node_states_neighbors * attention_scores_norm[:, tf.newaxis],

            segment_ids=edges[:, 0],

            num_segments=tf.shape(node_states)[0],

        )

        return out

class MultiHeadGraphAttention(layers.Layer):

    def __init__(self, units, num_heads=8, merge_type="concat", **kwargs):

        super().__init__(**kwargs)

        self.num_heads = num_heads

        self.merge_type = merge_type

        self.attention_layers = [GraphAttention(units) for _ in range(num_heads)]

    def call(self, inputs):

        atom_features, pair_indices = inputs

        # Obtain outputs from each attention head

        outputs = [

            attention_layer([atom_features, pair_indices])

            for attention_layer in self.attention_layers

        ]

        # Concatenate or average the node states from each head

        if self.merge_type == "concat":

            outputs = tf.concat(outputs, axis=-1)

        else:

            outputs = tf.reduce_mean(tf.stack(outputs, axis=-1), axis=-1)

        # Activate and return node states

        return tf.nn.relu(outputs)
منطق آموزش را با استفاده از متدهای سفارشی train_step، test_step و predict_step توجه کنید که مدل GAT در تمام فازها (آموزش، اعتبارسنجی و آزمون) بر روی کل گراف (یعنی وضعیت نودها و یال‌ها) عمل می‌کند. بنابراین، وضعیت نودها و یال‌ها به سازنده keras.Model ارسال شده و به عنوان ویژگی‌ها استفاده می‌شوند. تفاوت بین فازها در اندیس‌ها (و برچسب‌ها) است که خروجی‌های خاصی را جمع‌آوری می‌کند (tf.gather(outputs, indices)). این کلاس GraphAttentionNetwork یک زیرکلاس از keras.Model است که برای پیاده‌سازی شبکه GAT استفاده می‌شود. دارای متدهای train_step، test_step و predict_step می‌باشد که برای آموزش، ارزیابی و پیش‌بینی استفاده می‌شوند. در متد __init__، وضعیت نودها و یال‌ها به عنوان ورودی‌های سازنده گرفته می‌شوند و به عنوان ویژگی‌ها ذخیره می‌شوند. همچنین یک لایه Dense برای پیش‌پردازش ورودی‌ها، لایه‌های توجه چند سری و یک لایه Dense خروجی تعریف می‌شوند. در متد call، ورودی‌هایی که شامل وضعیت نودها و یال‌ها هستند، به عنوان ورودی به مدل داده می‌شوند و عملیات شبکه اعمال می‌شود. ابتدا ورودی‌ها از طریق لایه Dense پیش‌پردازش می‌شوند، سپس لایه‌های توجه چند سری روی ورودی‌ها اعمال می‌شوند و خروجی‌های نهایی توسط لایه Dense خروجی تبدیل می‌شوند. در متد train_step، ابتدا اندیس‌ها و برچسب‌ها از داده‌ی ورودی استخراج می‌شوند. سپس با استفاده از tf.GradientTape، عملیات پیش‌رو انجام می‌شود و مقدار خطا محاسبه می‌شود. سپس گرادیان خطا نسبت به وزن‌های قابل آموزش محاسبه می‌شود و با استفاده از بهینه‌ساز، گرادیان‌ها به وزن‌های قابل آموزش اعمال می‌شوند. در نهایت، مقادیر متریک‌های آموزشی به‌روزرسانی می‌شوند و نتیجه به‌صورت یک دیکشنری با نام متریک‌ها برگشت داده می‌شود. در متد predict_step، ابتدا اندیس‌ها از داده‌ی ورودی استخراج می‌شوند. سپس عملیات پیش‌رو بر روی داده‌های ورودی انجام می‌شود و احتمالات به‌دست آمده از طریق تابع softmax برگشت داده می‌شود. در متد test_step، ابتدا اندیس‌ها و برچسب‌ها از داده‌ی ورودی استخراج می‌شوند. سپس عملیات پیش‌رو بر روی داده‌های ورودی انجام می‌شود و خطا محاسبه می‌شود.
class GraphAttentionNetwork(keras.Model):

    def __init__(

        self,

        node_states,

        edges,

        hidden_units,

        num_heads,

        num_layers,

        output_dim,

        **kwargs,

    ):

        super().__init__(**kwargs)

        self.node_states = node_states

        self.edges = edges

        self.preprocess = layers.Dense(hidden_units * num_heads, activation="relu")

        self.attention_layers = [

            MultiHeadGraphAttention(hidden_units, num_heads) for _ in range(num_layers)

        ]

        self.output_layer = layers.Dense(output_dim)

    def call(self, inputs):

        node_states, edges = inputs

        x = self.preprocess(node_states)

        for attention_layer in self.attention_layers:

            x = attention_layer([x, edges]) + x

        outputs = self.output_layer(x)

        return outputs

    def train_step(self, data):

        indices, labels = data

        with tf.GradientTape() as tape:

            # Forward pass

            outputs = self([self.node_states, self.edges])

            # Compute loss

            loss = self.compiled_loss(labels, tf.gather(outputs, indices))

        # Compute gradients

        grads = tape.gradient(loss, self.trainable_weights)

        # Apply gradients (update weights)

        optimizer.apply_gradients(zip(grads, self.trainable_weights))

        # Update metric(s)

        self.compiled_metrics.update_state(labels, tf.gather(outputs, indices))

        return {m.name: m.result() for m in self.metrics}

    def predict_step(self, data):

        indices = data

        # Forward pass

        outputs = self([self.node_states, self.edges])

        # Compute probabilities

        return tf.nn.softmax(tf.gather(outputs, indices))

    def test_step(self, data):

        indices, labels = data

        # Forward pass

        outputs = self([self.node_states, self.edges])

        # Compute loss

        loss = self.compiled_loss(labels, tf.gather(outputs, indices))

        # Update metric(s)

        self.compiled_metrics.update_state(labels, tf.gather(outputs, indices))

        return {m.name: m.result() for m in self.metrics}

Train and evaluate:

در این بخش از کد، پارامترهای مورد نیاز برای آموزش مدل تعریف شده‌اند. سپس یک تابع هزینه، یک بهینه‌ساز و یک معیار دقت تعریف شده‌اند. همچنین از یک callback با نام EarlyStopping برای متوقف کردن زودهنگام آموزش استفاده می‌شود. سپس مدل GAT با استفاده از کلاس GraphAttentionNetwork ساخته می‌شود. سپس مدل کامپایل می‌شود با استفاده از تابع هزینه، بهینه‌ساز و معیار دقت. سپس مدل با استفاده از متد fit آموزش داده می‌شود. داده‌های آموزشی به همراه برچسب‌هایشان به عنوان ورودی به متد fit داده می‌شوند. همچنین نسبت اختصاص داده‌های اعتبارسنجی تعیین می‌شود، اندازه دسته، تعداد دورهای آموزش، callback های لازم و حالت verbose برای نمایش خروجی در حین آموزش نیز مشخص می‌شوند. پس از آموزش، دقت مدل روی داده‌های تست محاسبه شده و نتیجه چاپ می‌شود.
# Define hyper-parameters

HIDDEN_UNITS = 100

NUM_HEADS = 8

NUM_LAYERS = 3

OUTPUT_DIM = len(class_values)

NUM_EPOCHS = 100

BATCH_SIZE = 256

VALIDATION_SPLIT = 0.1

LEARNING_RATE = 3e-1

MOMENTUM = 0.9

loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

optimizer = keras.optimizers.SGD(LEARNING_RATE, momentum=MOMENTUM)

accuracy_fn = keras.metrics.SparseCategoricalAccuracy(name="acc")

early_stopping = keras.callbacks.EarlyStopping(

    monitor="val_acc", min_delta=1e-5, patience=5, restore_best_weights=True

)

# Build model

gat_model = GraphAttentionNetwork(

    node_states, edges, HIDDEN_UNITS, NUM_HEADS, NUM_LAYERS, OUTPUT_DIM

)

# Compile model

gat_model.compile(loss=loss_fn, optimizer=optimizer, metrics=[accuracy_fn])

gat_model.fit(

    x=train_indices,

    y=train_labels,

    validation_split=VALIDATION_SPLIT,

    batch_size=BATCH_SIZE,

    epochs=NUM_EPOCHS,

    callbacks=[early_stopping],

    verbose=2,

)

_, test_accuracy = gat_model.evaluate(x=test_indices, y=test_labels, verbose=0)

print("--" * 38 + f"\nTest Accuracy {test_accuracy*100:.1f}%")

Predict (probabilities):

در این قسمت از کد، مدل قبلی بر روی داده‌های تست استفاده می‌شود تا احتمالات پیش‌بینی برای هر نمونه در داده‌های تست محاسبه شود. ابتدا با استفاده از متد predict مدل، احتمالات پیش‌بینی برای داده‌های تست محاسبه می‌شوند و در متغیر test_probs ذخیره می‌شوند. سپس یک دیکشنری به نام mapping تعریف می‌شود که مقدار واقعی برچسب‌ها را به نام کلاس‌ها نگاشت می‌دهد. سپس با استفاده از حلقه for و تابع zip، برای هر نمونه در داده‌های تست، احتمالات پیش‌بینی و برچسب واقعی آن را به صورت زوجی در نظر می‌گیریم. در هر مرحله از حلقه، نمونه و شماره آن (به عنوان مثال ۱، ۲ و غیره) چاپ می‌شود، سپس برای هر احتمال پیش‌بینی و نام کلاس، احتمال پیش‌بینی و درصد آن چاپ می‌شود. در نهایت، بین هر نمونه یک خط تیره چاپ می‌شود تا خروجی برای هر نمونه متمایز شود.
test_probs = gat_model.predict(x=test_indices)

mapping = {v: k for (k, v) in class_idx.items()}

for i, (probs, label) in enumerate(zip(test_probs[:10], test_labels[:10])):

    print(f"Example {i+1}: {mapping[label]}")

    for j, c in zip(probs, class_idx.keys()):

        print(f"\tProbability of {c: <24} = {j*100:7.3f}%")

    print("---" * 20)
 

سوالات:

سوال ۱: چه نوع داده‌هایی برای ورودی شبکه توجه گراف (GAT) استفاده می‌شود؟ الف) داده‌های عددی ب) داده‌های متنی ج) داده‌های ساختار گرافی د) داده‌های تصویری سوال ۲: چه مکانیزمی برای محاسبه ضرایب توجه در GAT استفاده می‌شود؟ الف) مکانیزم توجه خطی ب) مکانیزم توجه خودمانند ج) مکانیزم توجه با استفاده از تابع فعال‌سازی ReLU د) مکانیزم توجه با استفاده از تابع فعال‌سازی Sigmoid سوال ۳: GAT برای چه وظیفه‌ای طراحی شده است؟ الف) طبقه‌بندی گره‌ها در داده‌های ساختار گرافی ب) تشخیص الگو در داده‌های تصویری ج) پردازش داده‌های متنی د) پیش‌بینی رویداد‌ها در داده‌های زمانی سوال ۴: چه نوع لایه‌ای برای طبقه‌بندی گره‌ها معمولاً در شبکه‌های عصبی گرافی استفاده می‌شود؟ الف) لایه پرسپترون ب) لایه توجه چند سر ج) لایه خودتوجه د) همه موارد بالا سوال ۵: GAT چگونه اطلاعات از همسایگان را جمع‌آوری می‌کند؟ الف) با استفاده از میانگین ساده و وزن‌دار ویژگی‌های همسایگان ب) با استفاده از مکانیزم توجه برای اختصاص دادن وزن‌دهی به همسایگان ج) با استفاده از لایه softmax برای طبقه‌بندی گره‌ها د) همه موارد