class_docstr: M = b1 * M + (1-b1) * G
  V = b2 * V + (1-b2) * G^2
  M_hat = M / (1 - beta1**t)
  V_hat = V / (1 - beta2**t)
  W = W - r * M_hat / sqrt(V_hat)
  
  https://paperswithcode.com/method/adam
code: def apply_gradients(self, W, G):
      if id(W) not in self.Vs:
          raise RuntimeError("Unregonized layer weights")
  
      with tf.name_scope("adam-apply-gradients"):
          beta1_pow = self.beta1_pow[id(W)]
          beta2_pow = self.beta2_pow[id(W)]
          tfe.assign(beta1_pow, beta1_pow.read_value() * self.beta1)
          tfe.assign(beta2_pow, beta2_pow.read_value() * self.beta2)
  
          M = self.Ms[id(W)]
          V = self.Vs[id(W)]
          for i in range(len(W)):
              tfe.assign(
                  M[i],
                  self.beta1 * M[i].read_value()
                  + (1 - self.beta1) * G[i],
              )
              tfe.assign(
                  V[i],
                  self.beta2 * V[i].read_value()
                  + (1 - self.beta2) * G[i] * G[i],
              )
              mhat = M[i].read_value() / (1 - beta1_pow.read_value())
              vhat = V[i].read_value() / (1 - beta2_pow.read_value())
              diff = (
                  self.learning_rate
                  * mhat
                  * tfe.inv_sqrt(vhat + self.epsilon)
              )
              tfe.assign(W[i], W[i].read_value() - diff)
  
file_path: tf-encrypted/tf_encrypted/keras/optimizers.py
imports: import numpy
  import tensorflow
  import tf_encrypted
label_desc: Use image node_id 3 for calling the Adam obj's underlying member method code with example usage: obj.apply_gradients(W, G) without return types