class_docstr: Negative sampling based skip-gram
class_docstr_tok: ["Negative","sampling","based","skip-gram"]
docstr: Learn a batch of random walks in a fast way. It has the following features:
      1. It calculating the gradients directly without the forward operation.
      2. It does sigmoid by a looking up table.
  
  Specifically, for each positive/negative node pair (i,j), the updating procedure is as following:
      score = self.fast_sigmoid(u_embedding[i].dot(v_embedding[j]))
      # label = 1 for positive samples; label = 0 for negative samples.
      u_embedding[i] += (label - score) * v_embedding[j]
      v_embedding[i] += (label - score) * u_embedding[j]
  
  Parameters
  ----------
  batch_walks list : a list of node sequnces
  lr float : current learning rate
  neg_nodes torch.LongTensor : a long tensor of sampled true negative nodes. If neg_nodes is None,
      then do negative sampling randomly from the nodes in batch_walks as an alternative.
  
  Usage example
  -------------
  batch_walks = [torch.LongTensor([1,2,3,4]),
                 torch.LongTensor([2,3,4,2])])
  lr = 0.01
  neg_nodes = None
docstr_tok: ["Learn","a","batch","of","random","walks","in","a","fast","way",".","It","has","the","following","features",":","1",".","It","calculating","the","gradients","directly","without","the","forward","operation",".","2",".","It","does","sigmoid","by","a","looking","up","table",".","Specifically",",","for","each","positive\/negative","node","pair","(","i",",","j",")",",","the","updating","procedure","is","as","following",":","score","=","self.fast_sigmoid","(","u_embedding","[","i","]",".dot","(","v_embedding","[","j","]",")",")","#","label","=","1","for","positive","samples",";","label","=","0","for","negative","samples",".","u_embedding","[","i","]","+=","(","label","-","score",")","*","v_embedding","[","j","]","v_embedding","[","i","]","+=","(","label","-","score",")","*","u_embedding","[","j","]","Parameters","--","--","--","--","--","batch_walks","list",":","a","list","of","node","sequnces","lr","float",":","current","learning","rate","neg_nodes","torch.LongTensor",":","a","long","tensor","of","sampled","true","negative","nodes",".","If","neg_nodes","is","None",",","then","do","negative","sampling","randomly","from","the","nodes","in","batch_walks","as","an","alternative",".","Usage","example","--","--","--","--","--","--","-","batch_walks","=","[","torch.LongTensor","(","[","1,2,3,4","]",")",",","torch.LongTensor","(","[","2,3,4,2","]",")","]",")","lr","=","0.01","neg_nodes","=","None"]
code: def fast_learn(self, batch_walks, neg_nodes=None):
      """Learn a batch of random walks in a fast way. It has the following features:
          1. It calculating the gradients directly without the forward operation.
          2. It does sigmoid by a looking up table.
  
      Specifically, for each positive/negative node pair (i,j), the updating procedure is as following:
          score = self.fast_sigmoid(u_embedding[i].dot(v_embedding[j]))
          # label = 1 for positive samples; label = 0 for negative samples.
          u_embedding[i] += (label - score) * v_embedding[j]
          v_embedding[i] += (label - score) * u_embedding[j]
  
      Parameters
      ----------
      batch_walks list : a list of node sequnces
      lr float : current learning rate
      neg_nodes torch.LongTensor : a long tensor of sampled true negative nodes. If neg_nodes is None,
          then do negative sampling randomly from the nodes in batch_walks as an alternative.
  
      Usage example
      -------------
      batch_walks = [torch.LongTensor([1,2,3,4]),
                     torch.LongTensor([2,3,4,2])])
      lr = 0.01
      neg_nodes = None
      """
      lr = self.lr
  
      # [batch_size, walk_length]
      if isinstance(batch_walks, list):
          nodes = torch.stack(batch_walks)
      elif isinstance(batch_walks, torch.LongTensor):
          nodes = batch_walks
      if self.only_gpu:
          nodes = nodes.to(self.device)
          if neg_nodes is not None:
              neg_nodes = neg_nodes.to(self.device)
      emb_u = (
          self.u_embeddings(nodes)
          .view(-1, self.emb_dimension)
          .to(self.device)
      )
      emb_v = (
          self.v_embeddings(nodes)
          .view(-1, self.emb_dimension)
          .to(self.device)
      )
  
      ## Postive
      bs = len(batch_walks)
      if bs < self.batch_size:
          index_emb_posu, index_emb_posv = init_emb2pos_index(
              self.walk_length, self.window_size, bs
          )
          index_emb_posu = index_emb_posu.to(self.device)
          index_emb_posv = index_emb_posv.to(self.device)
      else:
          index_emb_posu = self.index_emb_posu
          index_emb_posv = self.index_emb_posv
  
      # num_pos: the number of positive node pairs generated by a single walk sequence
      # [batch_size * num_pos, dim]
      emb_pos_u = torch.index_select(emb_u, 0, index_emb_posu)
      emb_pos_v = torch.index_select(emb_v, 0, index_emb_posv)
  
      pos_score = torch.sum(torch.mul(emb_pos_u, emb_pos_v), dim=1)
      pos_score = torch.clamp(pos_score, max=6, min=-6)
      # [batch_size * num_pos, 1]
      score = (1 - self.fast_sigmoid(pos_score)).unsqueeze(1)
      if self.record_loss:
          self.loss.append(
              torch.mean(self.fast_logsigmoid(pos_score)).item()
          )
  
      # [batch_size * num_pos, dim]
      if self.lap_norm > 0:
          grad_u_pos = score * emb_pos_v + self.lap_norm * (
              emb_pos_v - emb_pos_u
          )
          grad_v_pos = score * emb_pos_u + self.lap_norm * (
              emb_pos_u - emb_pos_v
          )
      else:
          grad_u_pos = score * emb_pos_v
          grad_v_pos = score * emb_pos_u
  
      if self.use_context_weight:
          if bs < self.batch_size:
              context_weight = init_weight(
                  self.walk_length, self.window_size, bs
              ).to(self.device)
          else:
              context_weight = self.context_weight
          grad_u_pos *= context_weight
          grad_v_pos *= context_weight
  
      # [batch_size * walk_length, dim]
      if bs < self.batch_size:
          grad_u, grad_v = init_empty_grad(
              self.emb_dimension, self.walk_length, bs
          )
          grad_u = grad_u.to(self.device)
          grad_v = grad_v.to(self.device)
      else:
          self.grad_u = self.grad_u.to(self.device)
          self.grad_u.zero_()
          self.grad_v = self.grad_v.to(self.device)
          self.grad_v.zero_()
          grad_u = self.grad_u
          grad_v = self.grad_v
      grad_u.index_add_(0, index_emb_posu, grad_u_pos)
      grad_v.index_add_(0, index_emb_posv, grad_v_pos)
  
      ## Negative
      if bs < self.batch_size:
          index_emb_negu, index_emb_negv = init_emb2neg_index(
              self.walk_length, self.window_size, self.negative, bs
          )
          index_emb_negu = index_emb_negu.to(self.device)
          index_emb_negv = index_emb_negv.to(self.device)
      else:
          index_emb_negu = self.index_emb_negu
          index_emb_negv = self.index_emb_negv
      emb_neg_u = torch.index_select(emb_u, 0, index_emb_negu)
  
      if neg_nodes is None:
          emb_neg_v = torch.index_select(emb_v, 0, index_emb_negv)
      else:
          emb_neg_v = self.v_embeddings.weight[neg_nodes].to(
              self.device
          )
  
      # [batch_size * walk_length * negative, dim]
      neg_score = torch.sum(torch.mul(emb_neg_u, emb_neg_v), dim=1)
      neg_score = torch.clamp(neg_score, max=6, min=-6)
      # [batch_size * walk_length * negative, 1]
      score = -self.fast_sigmoid(neg_score).unsqueeze(1)
      if self.record_loss:
          self.loss.append(
              self.negative
              * self.neg_weight
              * torch.mean(self.fast_logsigmoid(-neg_score)).item()
          )
  
      grad_u_neg = self.neg_weight * score * emb_neg_v
      grad_v_neg = self.neg_weight * score * emb_neg_u
  
      grad_u.index_add_(0, index_emb_negu, grad_u_neg)
      if neg_nodes is None:
          grad_v.index_add_(0, index_emb_negv, grad_v_neg)
  
      ## Update
      nodes = nodes.view(-1)
  
      # use adam optimizer
      grad_u = adam(
          grad_u,
          self.state_sum_u,
          nodes,
          lr,
          self.device,
          self.only_gpu,
      )
      grad_v = adam(
          grad_v,
          self.state_sum_v,
          nodes,
          lr,
          self.device,
          self.only_gpu,
      )
      if neg_nodes is not None:
          grad_v_neg = adam(
              grad_v_neg,
              self.state_sum_v,
              neg_nodes,
              lr,
              self.device,
              self.only_gpu,
          )
  
      if self.mixed_train:
          grad_u = grad_u.cpu()
          grad_v = grad_v.cpu()
          if neg_nodes is not None:
              grad_v_neg = grad_v_neg.cpu()
          else:
              grad_v_neg = None
  
          if self.async_update:
              grad_u.share_memory_()
              grad_v.share_memory_()
              nodes.share_memory_()
              if neg_nodes is not None:
                  neg_nodes.share_memory_()
                  grad_v_neg.share_memory_()
              self.async_q.put(
                  (grad_u, grad_v, grad_v_neg, nodes, neg_nodes)
              )
  
      if not self.async_update:
          self.u_embeddings.weight.data.index_add_(
              0, nodes.view(-1), grad_u
          )
          self.v_embeddings.weight.data.index_add_(
              0, nodes.view(-1), grad_v
          )
          if neg_nodes is not None:
              self.v_embeddings.weight.data.index_add_(
                  0, neg_nodes.view(-1), grad_v_neg
              )
      return
  
imports: import random
  import numpy
  import torch
  import torch.multiprocessing
  import torch.nn
  import torch.nn.functional
  from torch.multiprocessing import Queue
  from torch.nn import init
label_desc: Use image node_id 9 for calling the SkipGramModel obj's underlying member method code with example usage: obj.fast_learn(batch_walks, neg_nodes) without return types