alibaba / euler

A distributed graph deep learning framework.
Apache License 2.0
2.9k stars 558 forks source link

why loss=dh*h? #186

Open rayrayraykk opened 4 years ago

rayrayraykk commented 4 years ago

As the scalablegcn code shows:

def _optimize_store(self, node, node_embeddings):
    if not self.gradient_stores:
      return tf.zeros([]), tf.no_op()

    losses = []
    clear_ops = []
    for gradient_store, node_embedding in zip(
        self.gradient_stores, node_embeddings):
      embedding_gradient = tf.nn.embedding_lookup(gradient_store, node)
      with tf.control_dependencies([embedding_gradient]):
        clear_ops.append(
            utils_embedding.embedding_update(gradient_store, node,
                                             tf.zeros_like(embedding_gradient)))
      losses.append(tf.reduce_sum(node_embedding * embedding_gradient))

    store_loss = tf.add_n(losses)
    with tf.control_dependencies(clear_ops):
      return store_loss, self.store_optimizer.minimize(store_loss)
rayrayraykk commented 4 years ago

希望能够解答一下 谢谢@yangsiran @alinamimi

rayrayraykk commented 4 years ago

并且我使用

python -m tf_euler \
  --data_dir ppi \
  --max_id 56944 --feature_idx 1 --feature_dim 50 --label_idx 0 --label_dim 121 \
  --model scalable_gcn --mode train --fanouts 10,10

训练之后,再使用

python -m tf_euler \
  --data_dir ppi --id_file ppi/ppi_test.id \
  --max_id 56944 --feature_idx 1 --feature_dim 50 --label_idx 0 --label_dim 121 \
  --model scalable_gcn --mode evaluate  --fanouts 10,10

得到PPI F1 是0.76左右,远远高于你们wiki上写的0.57而我使用fanouts 10,10,10 F1却只有0.59

LIO-H-ZEN commented 4 years ago

最近花时间看完了Euler,来说下自己的理解: ) Motivation 首先,简单说下为什么需要缓存。对于中心节点来说,我们每个mini-batch都会计算它的各层图卷积表达(记作:H_1,..,H_k),其中H_k用于支持分类任务,H1,...,H{k-1}作为中间表达。对于经典GCN来说,我们分了mini-batch之后最大的挑战是,计算中心节点的H_1,...,H_k时需要依赖邻居节点的H1, .., H{k-1},这带来了指数级增长的计算需求。 Euler缓存的原理 Euler有2类缓存:store和gradient_store。store中按层缓存了全部节点的H1, .., H{k-1},先来说下store的更新逻辑

  1. 计算中心节点(如,A点)的H_1, .., H_k,所依赖的邻居节点的H1, ..., H{k-1}都直接从store中获取,并不重新计算了。
  2. 中心节点会将自己本地新计算出来的H1[A, :], ..., H{k-1}[A, :] push到server上覆盖对应的embedding。

那么我们为什么需要gradient_store呢? 这是因为,邻居节点的H1, .., H{k-1}均从store中直接pull出来的,并不是重新计算的,导致中心节点的supervised loss在bp的时候,梯度并不能传递到邻居节点的相关参数(邻居节点的node embedding,各层图卷积的权重W1,..,W{k-1})上。 下面说下gradient_store的更新逻辑,这里就涉及到楼主问的问题了,原谅我的长篇大论: )

  1. 中心节点会将store_gradient中缓存的本节点的梯度g取出来,然后利用g_1H1, ..., g{k-1}H_{k-1}作为store_loss,进行优化。优化store_loss时,对H求导,得到g,相当于,相比supervised loss本身给H带来的梯度之外,又额外加上了g。g是什么呢?g是当前中心节点给其他点做邻居时,其他点的supervised loss本应该可以传递给该节点的各层梯度。那么为什么要在它作为中心节点再更新呢?因为做邻居节点时各层H都是从缓存中pull出来的,无法将梯度bp到计算各层H所依赖的参数上。而当前中心节点的各层H都是重新计算的,这时将之前缓存的梯度加上,就可以将其bp给正确的参数。
  2. 计算Gradient(store_loss+ supervised loss, H[neighbor, : ]),更新到邻居节点的各层对应在gradient_store中。