class_docstr: M = b1 * M + (1-b1) * G
  V = b2 * V + (1-b2) * G^2
  V_hat = max(V_hat, V)
  W = W - r * M / sqrt(V_hat)
  
  Reference: https://paperswithcode.com/method/amsgrad
code: def apply_gradients(self, W, G):
      if id(W) not in self.Vs:
          raise RuntimeError("Unregonized layer weights")
  
      with tf.name_scope("AMSgard-apply-gradients"):
          M = self.Ms[id(W)]
          V = self.Vs[id(W)]
          Vhat = self.Vhats[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],
              )
              v_max = tfe.maximum(
                  Vhat[i].read_value(), V[i].read_value()
              )
              tfe.assign(Vhat[i], v_max)
              vhat_add_e = Vhat[i].read_value() + self.epsilon
              diff = (
                  self.learning_rate
                  * M[i].read_value()
                  * tfe.inv_sqrt(vhat_add_e)
              )
              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 AMSgrad obj's underlying member method code with example usage: obj.apply_gradients(W, G) without return types