|
|
|
@@ -9,7 +9,7 @@ import tqdm |
|
|
|
import autogl.data |
|
|
|
from .. import register_trainer |
|
|
|
from ..base import BaseNodeClassificationTrainer, EarlyStopping, Evaluation |
|
|
|
from ..evaluation import get_feval, Logloss, EvaluatorUtility |
|
|
|
from ..evaluation import get_feval, EvaluatorUtility, Logloss, MicroF1 |
|
|
|
from ..sampling.sampler.target_dependant_sampler import TargetDependantSampledData |
|
|
|
from ..sampling.sampler.neighbor_sampler import NeighborSampler |
|
|
|
from ..sampling.sampler.graphsaint_sampler import * |
|
|
|
@@ -21,351 +21,6 @@ from ...model import BaseModel |
|
|
|
LOGGER: logging.Logger = logging.getLogger("Node classification sampling trainer") |
|
|
|
|
|
|
|
|
|
|
|
# @register_trainer("NodeClassificationNeighborSampling") |
|
|
|
# class NodeClassificationNeighborSamplingTrainer(BaseNodeClassificationTrainer): |
|
|
|
# """ |
|
|
|
# The node classification trainer |
|
|
|
# for automatically training the node classification tasks |
|
|
|
# with neighbour sampling |
|
|
|
# """ |
|
|
|
# |
|
|
|
# def __init__( |
|
|
|
# self, |
|
|
|
# model: _typing.Union[BaseModel, str], |
|
|
|
# num_features: int, |
|
|
|
# num_classes: int, |
|
|
|
# optimizer: _typing.Union[_typing.Type[torch.optim.Optimizer], str, None] = None, |
|
|
|
# lr: float = 1e-4, |
|
|
|
# max_epoch: int = 100, |
|
|
|
# early_stopping_round: int = 100, |
|
|
|
# weight_decay: float = 1e-4, |
|
|
|
# device: _typing.Optional[torch.device] = None, |
|
|
|
# init: bool = True, |
|
|
|
# feval: _typing.Union[ |
|
|
|
# _typing.Sequence[str], _typing.Sequence[_typing.Type[Evaluation]] |
|
|
|
# ] = (Logloss,), |
|
|
|
# loss: str = "nll_loss", |
|
|
|
# lr_scheduler_type: _typing.Optional[str] = None, |
|
|
|
# **kwargs, |
|
|
|
# ) -> None: |
|
|
|
# if isinstance(optimizer, type) and issubclass(optimizer, torch.optim.Optimizer): |
|
|
|
# self._optimizer_class: _typing.Type[torch.optim.Optimizer] = optimizer |
|
|
|
# elif type(optimizer) == str: |
|
|
|
# if optimizer.lower() == "adam": |
|
|
|
# self._optimizer_class: _typing.Type[ |
|
|
|
# torch.optim.Optimizer |
|
|
|
# ] = torch.optim.Adam |
|
|
|
# elif optimizer.lower() == "adam" + "w": |
|
|
|
# self._optimizer_class: _typing.Type[ |
|
|
|
# torch.optim.Optimizer |
|
|
|
# ] = torch.optim.AdamW |
|
|
|
# elif optimizer.lower() == "sgd": |
|
|
|
# self._optimizer_class: _typing.Type[ |
|
|
|
# torch.optim.Optimizer |
|
|
|
# ] = torch.optim.SGD |
|
|
|
# else: |
|
|
|
# self._optimizer_class: _typing.Type[ |
|
|
|
# torch.optim.Optimizer |
|
|
|
# ] = torch.optim.Adam |
|
|
|
# else: |
|
|
|
# self._optimizer_class: _typing.Type[ |
|
|
|
# torch.optim.Optimizer |
|
|
|
# ] = torch.optim.Adam |
|
|
|
# |
|
|
|
# self._learning_rate: float = lr if lr > 0 else 1e-4 |
|
|
|
# self._lr_scheduler_type: _typing.Optional[str] = lr_scheduler_type |
|
|
|
# self._max_epoch: int = max_epoch if max_epoch > 0 else 1e2 |
|
|
|
# |
|
|
|
# self.__sampling_sizes: _typing.Sequence[int] = kwargs.get("sampling_sizes") |
|
|
|
# |
|
|
|
# self._weight_decay: float = weight_decay if weight_decay > 0 else 1e-4 |
|
|
|
# early_stopping_round: int = ( |
|
|
|
# early_stopping_round if early_stopping_round > 0 else 1e2 |
|
|
|
# ) |
|
|
|
# self._early_stopping = EarlyStopping( |
|
|
|
# patience=early_stopping_round, verbose=False |
|
|
|
# ) |
|
|
|
# super(NodeClassificationNeighborSamplingTrainer, self).__init__( |
|
|
|
# model, num_features, num_classes, device, init, feval, loss |
|
|
|
# ) |
|
|
|
# |
|
|
|
# self._valid_result: torch.Tensor = torch.zeros(0) |
|
|
|
# self._valid_result_prob: torch.Tensor = torch.zeros(0) |
|
|
|
# self._valid_score: _typing.Sequence[float] = [] |
|
|
|
# |
|
|
|
# self._hyper_parameter_space: _typing.Sequence[ |
|
|
|
# _typing.Dict[str, _typing.Any] |
|
|
|
# ] = [] |
|
|
|
# |
|
|
|
# self.__initialized: bool = False |
|
|
|
# if init: |
|
|
|
# self.initialize() |
|
|
|
# |
|
|
|
# def initialize(self) -> "NodeClassificationNeighborSamplingTrainer": |
|
|
|
# if self.__initialized: |
|
|
|
# return self |
|
|
|
# self.model.initialize() |
|
|
|
# self.__initialized = True |
|
|
|
# return self |
|
|
|
# |
|
|
|
# def get_model(self) -> BaseModel: |
|
|
|
# return self.model |
|
|
|
# |
|
|
|
# def __train_only(self, data) -> "NodeClassificationNeighborSamplingTrainer": |
|
|
|
# """ |
|
|
|
# The function of training on the given dataset and mask. |
|
|
|
# :param data: data of a specific graph |
|
|
|
# :return: self |
|
|
|
# """ |
|
|
|
# data = data.to(self.device) |
|
|
|
# optimizer: torch.optim.Optimizer = self._optimizer_class( |
|
|
|
# self.model.model.parameters(), |
|
|
|
# lr=self._learning_rate, |
|
|
|
# weight_decay=self._weight_decay, |
|
|
|
# ) |
|
|
|
# if type(self._lr_scheduler_type) == str: |
|
|
|
# if self._lr_scheduler_type.lower() == "step" + "lr": |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.StepLR = ( |
|
|
|
# torch.optim.lr_scheduler.StepLR(optimizer, step_size=100, gamma=0.1) |
|
|
|
# ) |
|
|
|
# elif self._lr_scheduler_type.lower() == "multi" + "step" + "lr": |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.MultiStepLR = ( |
|
|
|
# torch.optim.lr_scheduler.MultiStepLR( |
|
|
|
# optimizer, milestones=[30, 80], gamma=0.1 |
|
|
|
# ) |
|
|
|
# ) |
|
|
|
# elif self._lr_scheduler_type.lower() == "exponential" + "lr": |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.ExponentialLR = ( |
|
|
|
# torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.1) |
|
|
|
# ) |
|
|
|
# elif self._lr_scheduler_type.lower() == "ReduceLROnPlateau".lower(): |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.ReduceLROnPlateau = ( |
|
|
|
# torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, "min") |
|
|
|
# ) |
|
|
|
# else: |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.LambdaLR = ( |
|
|
|
# torch.optim.lr_scheduler.LambdaLR(optimizer, lambda _: 1.0) |
|
|
|
# ) |
|
|
|
# else: |
|
|
|
# lr_scheduler: torch.optim.lr_scheduler.LambdaLR = ( |
|
|
|
# torch.optim.lr_scheduler.LambdaLR(optimizer, lambda _: 1.0) |
|
|
|
# ) |
|
|
|
# |
|
|
|
# train_sampler: NeighborSampler = NeighborSampler( |
|
|
|
# data, self.__sampling_sizes, batch_size=20 |
|
|
|
# ) |
|
|
|
# |
|
|
|
# for current_epoch in range(self._max_epoch): |
|
|
|
# self.model.model.train() |
|
|
|
# """ epoch start """ |
|
|
|
# for target_node_indexes, edge_indexes in train_sampler: |
|
|
|
# optimizer.zero_grad() |
|
|
|
# data.edge_indexes = edge_indexes |
|
|
|
# prediction = self.model.model(data) |
|
|
|
# if not hasattr(torch.nn.functional, self.loss): |
|
|
|
# raise TypeError( |
|
|
|
# "PyTorch does not support loss type {}".format(self.loss) |
|
|
|
# ) |
|
|
|
# loss_function = getattr(torch.nn.functional, self.loss) |
|
|
|
# loss: torch.Tensor = loss_function( |
|
|
|
# prediction[target_node_indexes], data.y[target_node_indexes] |
|
|
|
# ) |
|
|
|
# loss.backward() |
|
|
|
# optimizer.step() |
|
|
|
# |
|
|
|
# if lr_scheduler is not None: |
|
|
|
# lr_scheduler.step() |
|
|
|
# |
|
|
|
# """ Validate performance """ |
|
|
|
# if hasattr(data, "val_mask") and getattr(data, "val_mask") is not None: |
|
|
|
# validation_results: _typing.Sequence[float] = self.evaluate( |
|
|
|
# (data,), "val", [self.feval[0]] |
|
|
|
# ) |
|
|
|
# |
|
|
|
# if self.feval[0].is_higher_better(): |
|
|
|
# validation_loss: float = -validation_results[0] |
|
|
|
# else: |
|
|
|
# validation_loss: float = validation_results[0] |
|
|
|
# self._early_stopping(validation_loss, self.model.model) |
|
|
|
# if self._early_stopping.early_stop: |
|
|
|
# LOGGER.debug("Early stopping at %d", current_epoch) |
|
|
|
# break |
|
|
|
# if hasattr(data, "val_mask") and data.val_mask is not None: |
|
|
|
# self._early_stopping.load_checkpoint(self.model.model) |
|
|
|
# return self |
|
|
|
# |
|
|
|
# def __predict_only(self, data): |
|
|
|
# """ |
|
|
|
# The function of predicting on the given data. |
|
|
|
# :param data: data of a specific graph |
|
|
|
# :return: the result of prediction on the given dataset |
|
|
|
# """ |
|
|
|
# data = data.to(self.device) |
|
|
|
# self.model.model.eval() |
|
|
|
# with torch.no_grad(): |
|
|
|
# prediction = self.model.model(data) |
|
|
|
# return prediction |
|
|
|
# |
|
|
|
# def train(self, dataset, keep_valid_result: bool = True): |
|
|
|
# """ |
|
|
|
# The function of training on the given dataset and keeping valid result. |
|
|
|
# :param dataset: |
|
|
|
# :param keep_valid_result: Whether to save the validation result after training |
|
|
|
# """ |
|
|
|
# data = dataset[0] |
|
|
|
# self.__train_only(data) |
|
|
|
# if keep_valid_result: |
|
|
|
# prediction: torch.Tensor = self.__predict_only(data) |
|
|
|
# self._valid_result: torch.Tensor = prediction[data.val_mask].max(1)[1] |
|
|
|
# self._valid_result_prob: torch.Tensor = prediction[data.val_mask] |
|
|
|
# self._valid_score = self.evaluate(dataset, "val") |
|
|
|
# |
|
|
|
# def predict_proba( |
|
|
|
# self, dataset, mask: _typing.Optional[str] = None, in_log_format: bool = False |
|
|
|
# ) -> torch.Tensor: |
|
|
|
# """ |
|
|
|
# The function of predicting the probability on the given dataset. |
|
|
|
# :param dataset: The node classification dataset used to be predicted. |
|
|
|
# :param mask: |
|
|
|
# :param in_log_format: |
|
|
|
# :return: |
|
|
|
# """ |
|
|
|
# data = dataset[0].to(self.device) |
|
|
|
# if mask is not None and type(mask) == str: |
|
|
|
# if mask.lower() == "train": |
|
|
|
# _mask = data.train_mask |
|
|
|
# elif mask.lower() == "test": |
|
|
|
# _mask = data.test_mask |
|
|
|
# elif mask.lower() == "val": |
|
|
|
# _mask = data.val_mask |
|
|
|
# else: |
|
|
|
# _mask = data.test_mask |
|
|
|
# else: |
|
|
|
# _mask = data.test_mask |
|
|
|
# result = self.__predict_only(data)[_mask] |
|
|
|
# return result if in_log_format else torch.exp(result) |
|
|
|
# |
|
|
|
# def predict(self, dataset, mask: _typing.Optional[str] = None) -> torch.Tensor: |
|
|
|
# return self.predict_proba(dataset, mask, in_log_format=True).max(1)[1] |
|
|
|
# |
|
|
|
# def get_valid_predict(self) -> torch.Tensor: |
|
|
|
# return self._valid_result |
|
|
|
# |
|
|
|
# def get_valid_predict_proba(self) -> torch.Tensor: |
|
|
|
# return self._valid_result_prob |
|
|
|
# |
|
|
|
# def get_valid_score(self, return_major: bool = True): |
|
|
|
# if return_major: |
|
|
|
# return (self._valid_score[0], self.feval[0].is_higher_better()) |
|
|
|
# else: |
|
|
|
# return (self._valid_score, [f.is_higher_better() for f in self.feval]) |
|
|
|
# |
|
|
|
# def get_name_with_hp(self) -> str: |
|
|
|
# name = "-".join( |
|
|
|
# [ |
|
|
|
# str(self._optimizer_class), |
|
|
|
# str(self._learning_rate), |
|
|
|
# str(self._max_epoch), |
|
|
|
# str(self._early_stopping.patience), |
|
|
|
# str(self.model), |
|
|
|
# str(self.device), |
|
|
|
# ] |
|
|
|
# ) |
|
|
|
# name = ( |
|
|
|
# name |
|
|
|
# + "|" |
|
|
|
# + "-".join( |
|
|
|
# [ |
|
|
|
# str(x[0]) + "-" + str(x[1]) |
|
|
|
# for x in self.model.get_hyper_parameter().items() |
|
|
|
# ] |
|
|
|
# ) |
|
|
|
# ) |
|
|
|
# return name |
|
|
|
# |
|
|
|
# def evaluate( |
|
|
|
# self, |
|
|
|
# dataset, |
|
|
|
# mask: _typing.Optional[str] = None, |
|
|
|
# feval: _typing.Union[ |
|
|
|
# None, _typing.Sequence[str], _typing.Sequence[_typing.Type[Evaluation]] |
|
|
|
# ] = None, |
|
|
|
# ) -> _typing.Sequence[float]: |
|
|
|
# data = dataset[0] |
|
|
|
# data = data.to(self.device) |
|
|
|
# if feval is None: |
|
|
|
# _feval: _typing.Sequence[_typing.Type[Evaluation]] = self.feval |
|
|
|
# else: |
|
|
|
# _feval: _typing.Sequence[_typing.Type[Evaluation]] = get_feval(list(feval)) |
|
|
|
# if mask.lower() == "train": |
|
|
|
# _mask = data.train_mask |
|
|
|
# elif mask.lower() == "test": |
|
|
|
# _mask = data.test_mask |
|
|
|
# elif mask.lower() == "val": |
|
|
|
# _mask = data.val_mask |
|
|
|
# else: |
|
|
|
# _mask = data.test_mask |
|
|
|
# prediction_probability: torch.Tensor = self.predict_proba(dataset, mask) |
|
|
|
# y_ground_truth = data.y[_mask] |
|
|
|
# |
|
|
|
# results = [] |
|
|
|
# for f in _feval: |
|
|
|
# try: |
|
|
|
# results.append(f.evaluate(prediction_probability, y_ground_truth)) |
|
|
|
# except: |
|
|
|
# results.append( |
|
|
|
# f.evaluate( |
|
|
|
# prediction_probability.cpu().numpy(), |
|
|
|
# y_ground_truth.cpu().numpy(), |
|
|
|
# ) |
|
|
|
# ) |
|
|
|
# return results |
|
|
|
# |
|
|
|
# def to(self, device: torch.device): |
|
|
|
# self.device = device |
|
|
|
# if self.model is not None: |
|
|
|
# self.model.to(self.device) |
|
|
|
# |
|
|
|
# def duplicate_from_hyper_parameter( |
|
|
|
# self, |
|
|
|
# hp: _typing.Dict[str, _typing.Any], |
|
|
|
# model: _typing.Union[BaseModel, str, None] = None, |
|
|
|
# ) -> "NodeClassificationNeighborSamplingTrainer": |
|
|
|
# |
|
|
|
# if model is None or not isinstance(model, BaseModel): |
|
|
|
# model = self.model |
|
|
|
# model = model.from_hyper_parameter( |
|
|
|
# dict( |
|
|
|
# [ |
|
|
|
# x |
|
|
|
# for x in hp.items() |
|
|
|
# if x[0] in [y["parameterName"] for y in model.hyper_parameter_space] |
|
|
|
# ] |
|
|
|
# ) |
|
|
|
# ) |
|
|
|
# |
|
|
|
# return NodeClassificationNeighborSamplingTrainer( |
|
|
|
# model, |
|
|
|
# self.num_features, |
|
|
|
# self.num_classes, |
|
|
|
# self._optimizer_class, |
|
|
|
# device=self.device, |
|
|
|
# init=True, |
|
|
|
# feval=self.feval, |
|
|
|
# loss=self.loss, |
|
|
|
# lr_scheduler_type=self._lr_scheduler_type, |
|
|
|
# **hp, |
|
|
|
# ) |
|
|
|
# |
|
|
|
# @property |
|
|
|
# def hyper_parameter_space(self): |
|
|
|
# return self._hyper_parameter_space |
|
|
|
# |
|
|
|
# @hyper_parameter_space.setter |
|
|
|
# def hyper_parameter_space(self, hp_space): |
|
|
|
# self._hyper_parameter_space = hp_space |
|
|
|
|
|
|
|
|
|
|
|
@register_trainer("NodeClassificationGraphSAINTTrainer") |
|
|
|
class NodeClassificationGraphSAINTTrainer(BaseNodeClassificationTrainer): |
|
|
|
def __init__( |
|
|
|
@@ -772,7 +427,7 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
init: bool = True, |
|
|
|
feval: _typing.Union[ |
|
|
|
_typing.Sequence[str], _typing.Sequence[_typing.Type[Evaluation]] |
|
|
|
] = (Logloss,), |
|
|
|
] = (MicroF1,), |
|
|
|
loss: str = "nll_loss", |
|
|
|
lr_scheduler_type: _typing.Optional[str] = None, |
|
|
|
**kwargs, |
|
|
|
@@ -815,17 +470,31 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
self._valid_result_prob: torch.Tensor = torch.zeros(0) |
|
|
|
self._valid_score: _typing.Sequence[float] = () |
|
|
|
|
|
|
|
self.__training_batch_size: int = kwargs.get("training_batch_size", 1024) |
|
|
|
if not self.__training_batch_size > 0: |
|
|
|
self.__training_batch_size: int = 1024 |
|
|
|
self.__predicting_batch_size: int = kwargs.get("predicting_batch_size", 1024) |
|
|
|
if not self.__predicting_batch_size > 0: |
|
|
|
self.__predicting_batch_size: int = 1024 |
|
|
|
|
|
|
|
cpu_count: int = os.cpu_count() if os.cpu_count() is not None else 0 |
|
|
|
self.__training_sampler_num_workers: int = kwargs.get( |
|
|
|
"training_sampler_num_workers", cpu_count |
|
|
|
) |
|
|
|
if self.__training_sampler_num_workers > cpu_count: |
|
|
|
self.__training_sampler_num_workers = cpu_count |
|
|
|
self.__predicting_sampler_num_workers: int = kwargs.get( |
|
|
|
"predicting_sampler_num_workers", cpu_count |
|
|
|
) |
|
|
|
if self.__predicting_sampler_num_workers > cpu_count: |
|
|
|
self.__predicting_sampler_num_workers = cpu_count |
|
|
|
|
|
|
|
super(NodeClassificationLayerDependentImportanceSamplingTrainer, self).__init__( |
|
|
|
model, num_features, num_classes, device, init, feval, loss |
|
|
|
) |
|
|
|
|
|
|
|
""" Set hyper parameters """ |
|
|
|
" Configure num_layers " |
|
|
|
self.__num_layers: int = kwargs.get("num_layers") |
|
|
|
" Configure sampled_node_size_budget " |
|
|
|
self.__sampled_node_size_budget: int = ( |
|
|
|
kwargs.get("sampled_node_size_budget") |
|
|
|
) |
|
|
|
self.__sampled_node_sizes: _typing.Sequence[int] = kwargs.get("sampled_node_sizes") |
|
|
|
|
|
|
|
self.__is_initialized: bool = False |
|
|
|
if init: |
|
|
|
@@ -846,10 +515,10 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
def get_model(self): |
|
|
|
return self.model |
|
|
|
|
|
|
|
def __train_only(self, data): |
|
|
|
def __train_only(self, integral_data): |
|
|
|
""" |
|
|
|
The function of training on the given dataset and mask. |
|
|
|
:param data: data of a specific graph |
|
|
|
:param integral_data: data of a specific graph |
|
|
|
:return: self |
|
|
|
""" |
|
|
|
optimizer: torch.optim.Optimizer = self._optimizer_class( |
|
|
|
@@ -886,56 +555,54 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
torch.optim.lr_scheduler.LambdaLR(optimizer, lambda _: 1.0) |
|
|
|
) |
|
|
|
|
|
|
|
sampled_node_size_budget: int = self.__sampled_node_size_budget |
|
|
|
num_layers: int = self.__num_layers |
|
|
|
|
|
|
|
__layer_dependent_importance_sampler: LayerDependentImportanceSampler = ( |
|
|
|
LayerDependentImportanceSampler(data.edge_index) |
|
|
|
) |
|
|
|
__top_layer_target_nodes_indexes: torch.LongTensor = ( |
|
|
|
torch.where(data.train_mask)[0].unique() |
|
|
|
LayerDependentImportanceSampler( |
|
|
|
integral_data.edge_index, torch.where(integral_data.train_mask)[0].unique(), |
|
|
|
self.__sampled_node_sizes, batch_size=self.__training_batch_size, |
|
|
|
num_workers=self.__training_sampler_num_workers |
|
|
|
) |
|
|
|
) |
|
|
|
for current_epoch in range(self._max_epoch): |
|
|
|
self.model.model.train() |
|
|
|
optimizer.zero_grad() |
|
|
|
""" epoch start """ |
|
|
|
" sample graphs " |
|
|
|
__layers: _typing.Sequence[ |
|
|
|
_typing.Tuple[torch.Tensor, torch.Tensor] |
|
|
|
] = __layer_dependent_importance_sampler.sample( |
|
|
|
__top_layer_target_nodes_indexes, |
|
|
|
[sampled_node_size_budget for _ in range(num_layers)] |
|
|
|
) |
|
|
|
data.edge_indexes = [layer[0] for layer in __layers] |
|
|
|
data.edge_weights = [layer[1] for layer in __layers] |
|
|
|
data = data.to(self.device) |
|
|
|
|
|
|
|
result: torch.Tensor = self.model.model.forward(data) |
|
|
|
if hasattr(torch.nn.functional, self.loss): |
|
|
|
loss_function = getattr( |
|
|
|
torch.nn.functional, self.loss |
|
|
|
for sampled_data in __layer_dependent_importance_sampler: |
|
|
|
optimizer.zero_grad() |
|
|
|
sampled_data: TargetDependantSampledData = sampled_data |
|
|
|
# 由于现在的Model设计是接受Data的,所以只能组装一个采样的Data作为参数 |
|
|
|
sampled_graph: autogl.data.Data = autogl.data.Data( |
|
|
|
x=integral_data.x[sampled_data.all_sampled_nodes_indexes], |
|
|
|
y=integral_data.y[sampled_data.all_sampled_nodes_indexes] |
|
|
|
) |
|
|
|
sampled_graph.to(self.device) |
|
|
|
sampled_graph.edge_indexes: _typing.Sequence[torch.LongTensor] = [ |
|
|
|
current_layer.edge_index_for_sampled_graph.to(self.device) |
|
|
|
for current_layer in sampled_data.sampled_edges_for_layers |
|
|
|
] |
|
|
|
prediction: torch.Tensor = self.model.model(sampled_graph) |
|
|
|
if not hasattr(torch.nn.functional, self.loss): |
|
|
|
raise TypeError( |
|
|
|
f"PyTorch does not support loss type {self.loss}" |
|
|
|
) |
|
|
|
loss_function = getattr(torch.nn.functional, self.loss) |
|
|
|
loss_value: torch.Tensor = loss_function( |
|
|
|
result[data.train_mask], |
|
|
|
data.y[data.train_mask] |
|
|
|
) |
|
|
|
else: |
|
|
|
raise TypeError( |
|
|
|
f"PyTorch does not support loss type {self.loss}" |
|
|
|
prediction[sampled_data.target_nodes_indexes.indexes_in_sampled_graph], |
|
|
|
sampled_graph.y[sampled_data.target_nodes_indexes.indexes_in_sampled_graph] |
|
|
|
) |
|
|
|
loss_value.backward() |
|
|
|
optimizer.step() |
|
|
|
|
|
|
|
loss_value.backward() |
|
|
|
optimizer.step() |
|
|
|
if self._lr_scheduler_type: |
|
|
|
lr_scheduler.step() |
|
|
|
|
|
|
|
if ( |
|
|
|
hasattr(data, "val_mask") and |
|
|
|
getattr(data, "val_mask") is not None and |
|
|
|
type(getattr(data, "val_mask")) == torch.Tensor |
|
|
|
hasattr(integral_data, "val_mask") and |
|
|
|
getattr(integral_data, "val_mask") is not None and |
|
|
|
type(getattr(integral_data, "val_mask")) == torch.Tensor |
|
|
|
): |
|
|
|
validation_results: _typing.Sequence[float] = self.evaluate( |
|
|
|
(data,), "val", [self.feval[0]] |
|
|
|
(integral_data,), "val", [self.feval[0]] |
|
|
|
) |
|
|
|
if self.feval[0].is_higher_better(): |
|
|
|
validation_loss: float = -validation_results[0] |
|
|
|
@@ -946,23 +613,68 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
LOGGER.debug("Early stopping at %d", current_epoch) |
|
|
|
break |
|
|
|
if ( |
|
|
|
hasattr(data, "val_mask") and |
|
|
|
getattr(data, "val_mask") is not None and |
|
|
|
type(getattr(data, "val_mask")) == torch.Tensor |
|
|
|
hasattr(integral_data, "val_mask") and |
|
|
|
getattr(integral_data, "val_mask") is not None and |
|
|
|
type(getattr(integral_data, "val_mask")) == torch.Tensor |
|
|
|
): |
|
|
|
self._early_stopping.load_checkpoint(self.model.model) |
|
|
|
|
|
|
|
def __predict_only(self, data) -> torch.Tensor: |
|
|
|
def __predict_only( |
|
|
|
self, integral_data, |
|
|
|
mask_or_target_nodes_indexes: _typing.Union[ |
|
|
|
torch.BoolTensor, torch.LongTensor |
|
|
|
] |
|
|
|
) -> torch.Tensor: |
|
|
|
""" |
|
|
|
The function of predicting on the given data. |
|
|
|
:param data: data of a specific graph |
|
|
|
:param integral_data: data of a specific graph |
|
|
|
:param mask_or_target_nodes_indexes: ... |
|
|
|
:return: the result of prediction on the given dataset |
|
|
|
""" |
|
|
|
data = data.to(self.device) |
|
|
|
if mask_or_target_nodes_indexes.dtype == torch.bool: |
|
|
|
target_nodes_indexes: _typing.Any = ( |
|
|
|
torch.where(mask_or_target_nodes_indexes)[0] |
|
|
|
) |
|
|
|
else: |
|
|
|
target_nodes_indexes: _typing.Any = mask_or_target_nodes_indexes.long() |
|
|
|
|
|
|
|
neighbor_sampler: NeighborSampler = NeighborSampler( |
|
|
|
torch_geometric.utils.add_remaining_self_loops(integral_data.edge_index)[0], |
|
|
|
target_nodes_indexes, [-1 for _ in self.__sampled_node_sizes], |
|
|
|
batch_size=self.__predicting_batch_size, |
|
|
|
num_workers=self.__predicting_sampler_num_workers, |
|
|
|
shuffle=False |
|
|
|
) |
|
|
|
|
|
|
|
prediction_batch_cumulative_builder = ( |
|
|
|
EvaluatorUtility.PredictionBatchCumulativeBuilder() |
|
|
|
) |
|
|
|
self.model.model.eval() |
|
|
|
with torch.no_grad(): |
|
|
|
predicted_x: torch.Tensor = self.model.model(data) |
|
|
|
return predicted_x |
|
|
|
for sampled_data in neighbor_sampler: |
|
|
|
sampled_data: TargetDependantSampledData = sampled_data |
|
|
|
sampled_graph: autogl.data.Data = autogl.data.Data( |
|
|
|
integral_data.x[sampled_data.all_sampled_nodes_indexes], |
|
|
|
integral_data.y[sampled_data.all_sampled_nodes_indexes] |
|
|
|
) |
|
|
|
sampled_graph.to(self.device) |
|
|
|
sampled_graph.edge_indexes: _typing.Sequence[torch.LongTensor] = [ |
|
|
|
current_layer.edge_index_for_sampled_graph.to(self.device) |
|
|
|
for current_layer in sampled_data.sampled_edges_for_layers |
|
|
|
] |
|
|
|
sampled_graph.edge_weights: _typing.Sequence[torch.FloatTensor] = [ |
|
|
|
current_layer.edge_weight.to(self.device) |
|
|
|
for current_layer in sampled_data.sampled_edges_for_layers |
|
|
|
] |
|
|
|
|
|
|
|
with torch.no_grad(): |
|
|
|
prediction_batch_cumulative_builder.add_batch( |
|
|
|
sampled_data.target_nodes_indexes.indexes_in_integral_graph.cpu().numpy(), |
|
|
|
self.model.model(sampled_graph)[ |
|
|
|
sampled_data.target_nodes_indexes.indexes_in_sampled_graph |
|
|
|
].cpu().numpy() |
|
|
|
) |
|
|
|
|
|
|
|
return torch.from_numpy(prediction_batch_cumulative_builder.compose()[1]) |
|
|
|
|
|
|
|
def predict_proba( |
|
|
|
self, dataset, mask: _typing.Optional[str]=None, |
|
|
|
@@ -978,16 +690,16 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
data = dataset[0].to(self.device) |
|
|
|
if mask is not None and type(mask) == str: |
|
|
|
if mask.lower() == "train": |
|
|
|
_mask: torch.Tensor = data.train_mask |
|
|
|
_mask: torch.BoolTensor = data.train_mask |
|
|
|
elif mask.lower() == "test": |
|
|
|
_mask: torch.Tensor = data.test_mask |
|
|
|
_mask: torch.BoolTensor = data.test_mask |
|
|
|
elif mask.lower() == "val": |
|
|
|
_mask: torch.Tensor = data.val_mask |
|
|
|
_mask: torch.BoolTensor = data.val_mask |
|
|
|
else: |
|
|
|
_mask: torch.Tensor = data.test_mask |
|
|
|
_mask: torch.BoolTensor = data.test_mask |
|
|
|
else: |
|
|
|
_mask: torch.Tensor = data.test_mask |
|
|
|
result = self.__predict_only(data)[_mask] |
|
|
|
_mask: torch.BoolTensor = data.test_mask |
|
|
|
result = self.__predict_only(data, _mask) |
|
|
|
return result if in_log_format else torch.exp(result) |
|
|
|
|
|
|
|
def predict(self, dataset, mask: _typing.Optional[str] = None) -> torch.Tensor: |
|
|
|
@@ -1021,18 +733,12 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
prediction_probability: torch.Tensor = self.predict_proba(dataset, mask) |
|
|
|
y_ground_truth: torch.Tensor = data.y[_mask] |
|
|
|
|
|
|
|
eval_results = [] |
|
|
|
for f in _feval: |
|
|
|
try: |
|
|
|
eval_results.append(f.evaluate(prediction_probability, y_ground_truth)) |
|
|
|
except: |
|
|
|
eval_results.append( |
|
|
|
f.evaluate( |
|
|
|
prediction_probability.cpu().numpy(), |
|
|
|
y_ground_truth.cpu().numpy(), |
|
|
|
) |
|
|
|
) |
|
|
|
return eval_results |
|
|
|
return [ |
|
|
|
f.evaluate( |
|
|
|
prediction_probability.cpu().numpy(), |
|
|
|
y_ground_truth.cpu().numpy(), |
|
|
|
) for f in _feval |
|
|
|
] |
|
|
|
|
|
|
|
def train(self, dataset, keep_valid_result: bool = True): |
|
|
|
""" |
|
|
|
@@ -1043,9 +749,9 @@ class NodeClassificationLayerDependentImportanceSamplingTrainer(BaseNodeClassifi |
|
|
|
data = dataset[0] |
|
|
|
self.__train_only(data) |
|
|
|
if keep_valid_result: |
|
|
|
prediction: torch.Tensor = self.__predict_only(data) |
|
|
|
self._valid_result: torch.Tensor = prediction[data.val_mask].max(1)[1] |
|
|
|
self._valid_result_prob: torch.Tensor = prediction[data.val_mask] |
|
|
|
prediction: torch.Tensor = self.__predict_only(data, data.val_mask) |
|
|
|
self._valid_result: torch.Tensor = prediction.max(1)[1] |
|
|
|
self._valid_result_prob: torch.Tensor = prediction |
|
|
|
self._valid_score: _typing.Sequence[float] = self.evaluate(dataset, "val") |
|
|
|
|
|
|
|
def get_valid_predict(self) -> torch.Tensor: |
|
|
|
@@ -1189,6 +895,25 @@ class NodeClassificationNeighborSamplingTrainer(BaseNodeClassificationTrainer): |
|
|
|
self._valid_result_prob: torch.Tensor = torch.zeros(0) |
|
|
|
self._valid_score: _typing.Sequence[float] = () |
|
|
|
|
|
|
|
self.__training_batch_size: int = kwargs.get("training_batch_size", 1024) |
|
|
|
if not self.__training_batch_size > 0: |
|
|
|
self.__training_batch_size: int = 1024 |
|
|
|
self.__predicting_batch_size: int = kwargs.get("predicting_batch_size", 1024) |
|
|
|
if not self.__predicting_batch_size > 0: |
|
|
|
self.__predicting_batch_size: int = 1024 |
|
|
|
|
|
|
|
cpu_count: int = os.cpu_count() if os.cpu_count() is not None else 0 |
|
|
|
self.__training_sampler_num_workers: int = kwargs.get( |
|
|
|
"training_sampler_num_workers", cpu_count |
|
|
|
) |
|
|
|
if self.__training_sampler_num_workers > cpu_count: |
|
|
|
self.__training_sampler_num_workers = cpu_count |
|
|
|
self.__predicting_sampler_num_workers: int = kwargs.get( |
|
|
|
"predicting_sampler_num_workers", cpu_count |
|
|
|
) |
|
|
|
if self.__predicting_sampler_num_workers > cpu_count: |
|
|
|
self.__predicting_sampler_num_workers = cpu_count |
|
|
|
|
|
|
|
super(NodeClassificationNeighborSamplingTrainer, self).__init__( |
|
|
|
model, num_features, num_classes, device, init, feval, loss |
|
|
|
) |
|
|
|
@@ -1257,15 +982,14 @@ class NodeClassificationNeighborSamplingTrainer(BaseNodeClassificationTrainer): |
|
|
|
|
|
|
|
neighbor_sampler: NeighborSampler = NeighborSampler( |
|
|
|
integral_data.edge_index, torch.where(integral_data.train_mask)[0].unique(), |
|
|
|
self.__sampling_sizes, batch_size=1024, |
|
|
|
num_workers=os.cpu_count() if os.cpu_count() is not None else 0 |
|
|
|
self.__sampling_sizes, batch_size=self.__training_batch_size, |
|
|
|
num_workers=self.__training_sampler_num_workers |
|
|
|
) |
|
|
|
for current_epoch in tqdm.tqdm(range(self._max_epoch), desc="Epoch"): |
|
|
|
for current_epoch in range(self._max_epoch): |
|
|
|
self.model.model.train() |
|
|
|
optimizer.zero_grad() |
|
|
|
""" epoch start """ |
|
|
|
" sample graphs " |
|
|
|
# todo: Done this |
|
|
|
for sampled_data in neighbor_sampler: |
|
|
|
optimizer.zero_grad() |
|
|
|
sampled_data: TargetDependantSampledData = sampled_data |
|
|
|
@@ -1339,7 +1063,8 @@ class NodeClassificationNeighborSamplingTrainer(BaseNodeClassificationTrainer): |
|
|
|
|
|
|
|
neighbor_sampler: NeighborSampler = NeighborSampler( |
|
|
|
integral_data.edge_index, target_nodes_indexes, [-1 for _ in self.__sampling_sizes], |
|
|
|
batch_size=1024, num_workers=0, shuffle=False |
|
|
|
batch_size=self.__predicting_batch_size, |
|
|
|
num_workers=self.__predicting_sampler_num_workers, shuffle=False |
|
|
|
) |
|
|
|
|
|
|
|
prediction_batch_cumulative_builder = ( |
|
|
|
|