Tensorflow:在同一操作中为两个不同的变量共享值

3
最近我在尝试使用TensorFlow (TF),遇到一个问题:假设我想计算函数

f(x) = \sum_{ijk} x_i x_j x_k

的值和梯度。其中x的索引不同,但均指向同一向量bold x ,而J是随机常数(在物理学中这是自旋玻璃模型)。相对于x_k 的梯度简单地为

grad_k(x) = sum_ij x_i*x_j

因此,f对N^3个项求和,而gradf对N^2项求和N次。我通过生成所有求和的项作为秩为3的张量,并在所有条目上进行汇总约减来实现f。然后,为了进行微分,我应用了以下操作:

tf.gradients(f, xk)[0]

其中f是损失函数,xk是一个变量。以下是一个MWE,假设所有的J都为1。

import numpy as np
import tensorflow as tf

#first I define the variable                                                                                                                                                                  
n=10 #size of x                                                                                                                                                                               
x1 = tf.Variable(tf.zeros([n], dtype='float64'))
x2 = tf.placeholder(tf.float64, shape=[n])

#here I define the cost function                                                                                                                                                              
f_tensor = tf.mul(tf.mul(tf.reshape(x1, [n]),
                         tf.reshape(x2, [n,1])),
                  tf.reshape(x2, [n,1,1]))
f = tf.reduce_sum(f_tensor)

session = tf.Session()
init = tf.initialize_all_variables()
session.run(init)

#run on test array                                                                                                                                                                            
xtest = np.ones(n)
res = session.run([f, tf.gradients(f, x1)[0]],
                  feed_dict={x1 : xtest,
                             x2 : xtest})

assert res[0] == 1000
assert all(res[1] == np.array([100 for _ in xrange(n)]))

我需要独立多次调用run方法,并希望将变量赋值的次数减少到只有一次,因为x1和x2指向同一个向量。

对于n=200的相关示例进行了一些分析(在GeForce GTX 650上),结果显示:

  • cuMemcpyDtoHAsync 占用63%的时间
  • cuMemcpyHtoDAsync 占用18%,以及
  • cuEventRecord 占用18%。

(这个MWE的结果类似)

因此,在GPU上执行计算时,赋值操作是最昂贵的操作。显然,随着n的增加,开销会变得更糟,从而部分抵消使用GPU的好处。

你有什么建议可以减少传输X的开销吗?

同时,如果您有任何其他减少开销的建议,那将不胜感激。

编辑

为了展示问题,我将按照的建议替换所有x2实例为x1,则MWE如下所示

#first I define the variable                                                                                                                                                                  
n=10 #size of x                                                                                                                                                                               
x1 = tf.Variable(tf.zeros([n], dtype='float64'))

#here I define the cost function                                                                                                                                                              
f_tensor = tf.mul(tf.mul(tf.reshape(x1, [n]),
                         tf.reshape(x1, [n,1])),
                  tf.reshape(x1, [n,1,1]))
f = tf.reduce_sum(f_tensor)

session = tf.Session()
init = tf.initialize_all_variables()
session.run(init)

#run on test array                                                                                                                                                                            
xtest = np.ones(n)
session.run(x1.assign(xtest))
res = session.run([f, tf.gradients(f, x1)[0]])

assert res[0] == 1000
for g in res[1]:
    assert g == 100

第二个断言会失败,因为梯度的每个条目都应该是100而不是300。原因是xi,xj,xk都引用同一个向量,但它们在符号上是不同的:如果将所有x替换为相同的变量,则会得到x^3的导数(即3*x^2),这就是第二个MWE的结果。

P.S. 为了清晰起见,我还明确地分配了x1。


如果您始终为x1x2提供相同的向量,是否需要定义两个单独的张量?例如,如果删除x2的定义并将所有引用x2的地方替换为x1,我认为您的程序将具有相同的语义。 - mrry
这段代码对我来说似乎是有效的:https://gist.github.com/mrry/b8f903c8a276f3ed6ebe。也许这里的代码和你正在运行的真实代码之间存在一些差异? - mrry
1
你能否更新 MWE,以展示当你应用我的建议更改时,你的真实程序失败了吗?我认为我们可能在使用“赋值”这个词时有所不同。(此外,如果没有变量赋值(即 x1.assign(…)),那么程序中为什么会有一个 tf.Variable 是不清楚的。) - mrry
好的,我会进行编辑。同时,x1是一个变量,因为我对它进行了导数运算,而我没有对x2进行导数运算,因此它们在逻辑上是不同的。我可以每次显式地分配x1,并使用feed_dict设置x2。但请注意,如果我想使用GradientDescentOptimize来最小化损失函数,那么这样做是行不通的,因为为了使其起作用,我必须以相同的方式更新x1和x2,但只对x1进行导数运算。我们回到了最初的问题。 - stefano
是的,我应该修复那个问题,不过我在第二个例子中已经修复了。 - stefano
显示剩余4条评论
2个回答

2

实现您想要的结果的一种方法是使用tf.stop_gradient()操作,使变量x1的副本高效复制,而不会对梯度有贡献:

import numpy as np
import tensorflow as tf

# First define the variable.
n = 10 # size of x                                                                                                                                                                               
x1 = tf.Variable(tf.zeros([n], dtype=tf.float64))
x2 = tf.stop_gradient(x1)

# Now define the cost function                                                                                                                                                              
f_tensor = tf.mul(tf.mul(tf.reshape(x1, [n]),
                         tf.reshape(x2, [n,1])),
                  tf.reshape(x2, [n,1,1]))
f = tf.reduce_sum(f_tensor)

session = tf.Session()
init = tf.initialize_all_variables()
session.run(init)

# Run on test array                                                                                                                                                                            
xtest = np.ones(n)
res = session.run([f, tf.gradients(f, x1)[0]],
                  feed_dict={x1 : xtest})

assert res[0] == 1000
for g in res[1]:
    assert g == 100

太棒了,就是这样! - stefano
如果我现在将f更改为f = tf.reduce_sum(tf.mul(J, f_tensor)),其中J是一个声明为变量的秩为3的张量,其形状与f_tensor相同,那么是否可以问一个相关的问题?每次调用session.run时,J会被复制回主机吗?因为我仍然观察到cuMemcpyDtoHAsync占总时间的64%。有没有一种有效的方法将J放在GPU上并保持在那里,直到会话关闭? - stefano
如果J是分配给GPU设备的变量,在调用session.run()时它不会被复制回主机(除非您将其添加到获取参数列表中)。您的计算总共需要多长时间?由于所有操作都是异步执行(将内核发布到GPU流),因此将结果复制回主机所需的时间可能包括计算结果的时间,而最终的复制操作必须阻塞。 - mrry
J并没有被获取,它就像x1(不是形状),只是我在每次调用session.run()时不重新分配它,所以当调用init = tf.initialize_all_variables(); session.run(init)时,它应该被复制到GPU上。在GeForce GTX 650上,对于n=200ncalls=100的评估,计算需要20秒。有时在最后还会抱怨“将pool_size_limit_从100提高到110”,但是当进行ncalls=10时却没有这种情况。不确定那是什么原因。 - stefano

1
我无法在上面发表评论(声望不够),但请注意,分析梯度应为 $$ \frac{\partial f}{\partial x_k} = \sum_{ij} J_{ijk} x_i x_j + \sum_{ij} J_{ikj} x_i x_j + \sum_{ij} J_{kij} x_i x_j. $$

Kronecker积的导数 - stefano
我也犯了完全相同的错误 :) - Pratik C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接