在Theano中从扫描中调用函数

5

我需要通过scan多次执行一个Theano函数,以便将代价函数求和并在梯度计算中使用。虽然我熟悉深度学习教程中的此类操作,但我的数据切片和其他一些复杂性意味着我需要以稍微不同的方式进行操作。 以下是我尝试做的事情的大大简化版本。

tn = testnet()
cost = tn.single_cost( )
x = theano.shared(numpy.asarray([7.1,2.2,3.4], dtype='float32'))
index = T.lscalar('index')
test_fn = theano.function(inputs=[index], outputs=cost, 
    givens={tn.x:x[index:index+1]} )

def step(curr):
    return T.constant( test_fn( curr ) )
outs,_ = theano.scan(step, T.arange(2))

out_fn = theano.function(inputs=[], outputs=outs)
print out_fn()

在扫描函数中,对test_fn(curr)的调用出现错误... 期望一个类似数组的对象,但发现一个变量:你可能正在尝试在(可能共享的)变量而不是数字数组上调用函数?')
即使我传递一个值数组而不是将T.arrange(2)放在原处,我仍然会收到相同的错误。在扫描函数中是否不能调用函数?
总的来说,我想知道是否有一种方法可以使用一系列索引调用这样的函数,以便输出可以输入到T.grad()计算中(没有显示)。
3个回答

3
不要创建两个不同的theano.functions。theano.function会接受符号关系,进行优化并编译它。你现在正在要求theano.scan(因此是out_fn)将编译后的函数视为符号关系。我不确定你是否可以在技术上让它正常工作,但这与Theano的理念相悖。由于我不知道你的成本函数在这里做了什么,所以我不能给出确切的例子,但下面是一个可以正常运行且与您尝试执行的操作类似的快速示例。
x = theano.shared(np.asarray([7.1,2.2,3.4], dtype = np.float32))

v = T.vector("v")
def fv(v):
    res,_ = theano.scan(lambda x: x ** 2, v)
    return T.sum(res)

def f(i):
    return fv(x[i:i+2])

outs,_ = theano.scan(
    f, 
    T.arange(2)
    )

fn = theano.function(
    [],
    outs,
    )

fn()

1

经过一些调查,我认为从一个函数中调用另一个函数是不正确的。这段代码的挑战在于,遵循深度学习教程的基本设计,网络的第一层具有一个符号变量作为输入,并且输出向上传播到更高的层,直到从顶层计算出最终成本。教程使用类似以下的代码...

class layer1(object):
   def __init__(self):
      self.x = T.matrix()
      self.output = activation(T.dot(self.x,self.W) + self.b)

对我来说,张量变量(layer1.self.x)需要在每次扫描时更改,以获得新的数据片段。函数中的“givens”语句可以实现这一点,但由于从“scan”内部调用编译好的theano函数不起作用,因此我能够找到两个其他的解决方案...
1- 重新设计网络,使其成本函数基于一系列函数调用而不是传播变量。这在技术上很简单,但需要一些重新编码,以使多层网络中的事物组织得当。
2- 在扫描内部使用theano.clone。该代码看起来像...
def step(curr):
    y_in = y[curr]
    replaces = {tn.layer1.x : x[curr:curr+1]}
    fn = theano.clone(tn.cost(y_in), replace=replaces)
    return fn
outs,_ = theano.scan(step, sequences=[T.arange(batch_start,batch_end)])

这两种方法返回相同的结果,并且执行速度相同。


@bivuac0 -- 我遇到了和你一样的问题,看起来是同一种方式,即试图修改深度学习示例。我怀疑我们正在尝试做类似的事情。在我的情况下,我想使用RBM和SdA示例来基于一个大型数据集训练网络,该数据集稀疏或CPU,我将其分批复制到GPU上,变得稠密。如果你能详细说明解决这个问题时学到了什么,以及不同的方法如何发挥作用,我会非常感激。 - Bob
Bob:上面直接的答案是我在研究这个问题时所学到的内容。我提出的两个解决方案是我想出来的。我很乐意尝试帮助您解决具体的问题,但我需要了解更多细节。您可以直接从我的个人资料中向我提问,但也有一个非常活跃的“ Theano-users”群组在Google-groups上,您也可以在那里得到答案。 - bivouac0
1
谢谢。今天下午我最终在列表上发布了。扫描/克隆策略在我尝试找出处理更新的方法时立即崩溃了。 - Bob

0

解决方案

标准方法是使用OpFromGraph(从0.8.2版本开始)

import theano as th
import theano.tensor as T

x = T.scalar('x')
y = T.scalar('y')
z = x+y
# unlike theano.function, must use list for outputs
op_add = th.OpFromGraph([x,y], [z])

def my_add(x_, y_):
    return op_add(x_, y_)[0]

x_list = T.vector('x_li')
x_sum = th.scan(op_add, sequences=[x_list], outputs_info=[T.constant(0.)])
fn_sum = th.function([x_list], x_sum)
fn([1., 2., 3., 4.]) # 10.

它是做什么的?

OpFromGraph 将从图中定义的函数编译,然后打包成一个新的 Op。就像在命令式编程语言中定义函数一样。

优缺点

  • [+] 在复杂模型中非常方便。
  • [+] 它可以节省编译时间。您可以将大模型中常用的部分编译成 OpFromGraph,然后直接在更大的模型中使用它。最终图形将比直接实现少一些节点。
  • [-] 它会导致更差的运行时性能。调用函数有开销,而且编译器由于其编译性质无法进行全局优化。
  • [-] 它还不够成熟,仍在开发中。它的文档不完整。目前不支持 theano.function 中的 updatesgivens

注意事项

在大多数情况下,您应该定义 Python 函数/类来构建模型。只有在没有其他解决方法或者想要节省编译时间时才使用 OpFromGraph


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