class_docstr: Encoder based on dgl.nn.NNConv & GRU & dgl.nn.set2set pooling
  Parameters
  -----------
  in_dim: int
      Input feature size.
  hid_dim: int
      Hidden feature size.
  
  Functions
  -----------
  forward(graph, nfeat, efeat):
      graph: DGLGraph
      nfeat: Tensor
          [N * D1], node features
      efeat: Tensor
          [E * D2], edge features
code: def forward(self, graph, nfeat, efeat):
      out = F.relu(self.lin0(nfeat))
      h = out.unsqueeze(0)
  
      feat_map = []
  
      # Convolution layer number is 3
      for i in range(3):
          m = F.relu(self.conv(graph, out, efeat))
          out, h = self.gru(m.unsqueeze(0), h)
          out = out.squeeze(0)
          feat_map.append(out)
  
      out = self.set2set(graph, out)
  
      # out: global embedding, feat_map[-1]: local embedding
      return out, feat_map[-1]
  
imports: import torch
  import torch.nn
  import torch.nn.functional
  from dgl.nn import GINConv, NNConv, Set2Set
  from dgl.nn.pytorch.glob import SumPooling
  from torch.nn import BatchNorm1d, GRU, Linear, ModuleList, ReLU, Sequential
  from utils import global_global_loss_, local_global_loss_
label_desc: Use image node_id 2 for calling the NNConvEncoder obj's underlying member method code with example usage: obj.forward(graph, nfeat, efeat) and returns: out,  feat_map