class_docstr: Description
  -----------
  Boost GNN predictor for semi-supervised node classification or regression problems.
  Publication: https://arxiv.org/abs/2101.08543
  
  Parameters
  ----------
  gnn_model : nn.Module
      DGL implementation of GNN model.
  task: str, optional
      Regression or classification task.
  loss_fn : callable, optional
      Function that takes torch tensors, pred and true, and returns a scalar.
  trees_per_epoch : int, optional
      Number of GBDT trees to build each epoch.
  backprop_per_epoch : int, optional
      Number of backpropagation steps to make each epoch.
  lr : float, optional
      Learning rate of gradient descent optimizer.
  append_gbdt_pred : bool, optional
      Append GBDT predictions or replace original input node features.
  train_input_features : bool, optional
      Train original input node features.
  gbdt_depth : int, optional
      Depth of each tree in GBDT model.
  gbdt_lr : float, optional
      Learning rate of GBDT model.
  gbdt_alpha : int, optional
      Weight to combine previous and new GBDT trees.
  random_seed : int, optional
      random seed for GNN and GBDT models.
  
  Examples
  ----------
  gnn_model = GAT(10, 20, num_heads=5),
  bgnn = BGNNPredictor(gnn_model)
  metrics = bgnn.fit(graph, X, y, train_mask, val_mask, test_mask, cat_features)
code: def evaluate_model(self, logits, target_labels, mask):
      metrics = {}
      y = target_labels[mask]
      with torch.no_grad():
          pred = logits[mask]
          if self.task == "regression":
              metrics["loss"] = torch.sqrt(
                  F.mse_loss(pred, y).squeeze() + 1e-8
              )
              metrics["rmsle"] = torch.sqrt(
                  F.mse_loss(
                      torch.log(pred + 1), torch.log(y + 1)
                  ).squeeze()
                  + 1e-8
              )
              metrics["mae"] = F.l1_loss(pred, y)
              metrics["r2"] = torch.Tensor(
                  [r2_score(y.cpu().numpy(), pred.cpu().numpy())]
              )
          elif self.task == "classification":
              metrics["loss"] = F.cross_entropy(pred, y.long())
              metrics["accuracy"] = torch.Tensor(
                  [(y == pred.max(1)[1]).sum().item() / y.shape[0]]
              )
  
          return metrics
  
imports: import itertools
  import time
  from collections import defaultdict
  import numpy
  import pandas
  import torch
  import torch.nn.functional
  from catboost import CatBoostClassifier, CatBoostRegressor, Pool, sum_models
  from sklearn import preprocessing
  from sklearn.metrics import r2_score
  from tqdm import tqdm
label_desc: Use image node_id 11 for calling the BGNNPredictor obj's underlying member method code with example usage: obj.evaluate_model(logits, target_labels, mask) and returns: metrics