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 __init__(
      self,
      gnn_model,
      task="regression",
      loss_fn=None,
      trees_per_epoch=10,
      backprop_per_epoch=10,
      lr=0.01,
      append_gbdt_pred=True,
      train_input_features=False,
      gbdt_depth=6,
      gbdt_lr=0.1,
      gbdt_alpha=1,
      random_seed=0,
  ):
      self.device = torch.device(
          "cuda:0" if torch.cuda.is_available() else "cpu"
      )
  
      self.model = gnn_model.to(self.device)
      self.task = task
      self.loss_fn = loss_fn
      self.trees_per_epoch = trees_per_epoch
      self.backprop_per_epoch = backprop_per_epoch
      self.lr = lr
      self.append_gbdt_pred = append_gbdt_pred
      self.train_input_features = train_input_features
      self.gbdt_depth = gbdt_depth
      self.gbdt_lr = gbdt_lr
      self.gbdt_alpha = gbdt_alpha
      self.random_seed = random_seed
      torch.manual_seed(random_seed)
      np.random.seed(random_seed)
  
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 1 to create a new BGNNPredictor object  with example: obj = BGNNPredictor(gnn_model, task, loss_fn, trees_per_epoch, backprop_per_epoch, lr, append_gbdt_pred, train_input_features, gbdt_depth, gbdt_lr, gbdt_alpha, random_seed)