Theano2.1.16-基础知识之调试:常见的问题解答

来自:http://deeplearning.net/software/theano/tutorial/shape_info.html

Debugging Theano: FAQ and Troubleshooting

    在计算机程序中会有许多种不同的bug。该页就是来说说FAQ,即问题集的。介绍了一些处理常见问题的方法,并介绍了一些在我们自己的theano代码中,用于查找问题(即使该问题发生在theano内部)的工具: Using DebugMode.

一、将问题独立出来/测试theano的编译器

    你可以在 DebugMode 下运行thenao的函数。该模式下会测试theano的优化,并有助于找到问题的所在,例如NaN,inf 和其他问题。

二、分析错误信息

    甚至在默认的配置下,theano都会尝试显示有用的错误信息。考虑下面的错误代码:

import numpy as np
import theano
import theano.tensor as T

x = T.vector()
y = T.vector()
z = x + x
z = z + y
f = theano.function([x, y], z)
f(np.ones((2,)), np.ones((3,)))
运行上面的代码:

Traceback (most recent call last):
  File "test0.py", line 10, in <module>
    f(np.ones((2,)), np.ones((3,)))
  File "/PATH_TO_THEANO/theano/compile/function_module.py", line 605, in __call__
    self.fn.thunks[self.fn.position_of_error])
  File "/PATH_TO_THEANO/theano/compile/function_module.py", line 595, in __call__
    outputs = self.fn()
ValueError: Input dimension mis-match. (input[0].shape[0] = 3, input[1].shape[0] = 2)
Apply node that caused the error: Elemwise{add,no_inplace}(<TensorType(float64, vector)>, <TensorType(float64, vector)>, <TensorType(float64, vector)>)
Inputs types: [TensorType(float64, vector), TensorType(float64, vector), TensorType(float64, vector)]
Inputs shapes: [(3,), (2,), (2,)]
Inputs strides: [(8,), (8,), (8,)]
Inputs scalar values: ['not scalar', 'not scalar', 'not scalar']

HINT: Re-running with most Theano optimization disabled could give you a back-traces when this node was created. This can be done with by setting the Theano flags 'optimizer=fast_compile'. If that does not work, Theano optimization can be disabled with 'optimizer=None'.
HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint of this apply node.

    可以说最有用的信息通常差不多一半都来自对错误信息的分析理解,而且错误信息也是按照引起错误的顺序显示的 (ValueError: 输入维度不匹配. (input[0].shape[0] = 3, input[1].shape[0] = 2).。在它下面,给出了一些其他的信息,例如apply节点导致的错误,还有输入类型,shapes,strides 和scalar values。

    最后两个提示在调试的时候也是很有用的。使用theano flag optimizer=fast_compile 或者 optimizer=None 可以告诉你出错的那一行,而 exception_verbosity=high 会显示apply节点的调试打印(debugprint)。使用这些提示,错误信息最后会变成:

Backtrace when the node is created:
  File "test0.py", line 8, in <module>
    z = z + y

Debugprint of the apply node:
Elemwise{add,no_inplace} [@A] <TensorType(float64, vector)> ''
 |Elemwise{add,no_inplace} [@B] <TensorType(float64, vector)> ''
 | |<TensorType(float64, vector)> [@C] <TensorType(float64, vector)>
 | |<TensorType(float64, vector)> [@C] <TensorType(float64, vector)>
 |<TensorType(float64, vector)> [@D] <TensorType(float64, vector)>
    这里我们可以看到错误可以追溯到 z = z + y这一行 。对于这个例子来说,使用 optimizer=fast_compile 是有效果的,如果它没效果,你就需要设置 optimizer=None 或者使用测试值。

三、使用测试值

   在 v.0.4.0版本的时候,Theano有一个新机制,也就是theano.function 编译之前,graph是动态执行的。因为优化在这个阶段还没执行,所以对于用户来说就很容易定位bug的来源。这个功能可以通过配置flagtheano.config.compute_test_value启用。下面这个例子就很好的说明了这点。这里,我们使用exception_verbosity=high 和 optimizer=fast_compile,这里(个人:该例子中)不会告诉你具体出错的那一行(个人在;这里与上面有些矛盾,不过看得出来这里提示出错的是调用的函数,而上面出错定位到了语句。具体的留待以后在分析)。 optimizer=None 因而就很自然的用来代替测试值了。

import numpy
import theano
import theano.tensor as T

# compute_test_value is 'off' by default, meaning this feature is inactive
theano.config.compute_test_value = 'off' # Use 'warn' to activate this feature

# configure shared variables
W1val = numpy.random.rand(2, 10, 10).astype(theano.config.floatX)
W1 = theano.shared(W1val, 'W1')
W2val = numpy.random.rand(15, 20).astype(theano.config.floatX)
W2 = theano.shared(W2val, 'W2')

# input which will be of shape (5,10)
x  = T.matrix('x')
# provide Theano with a default test-value
#x.tag.test_value = numpy.random.rand(5, 10)

# transform the shared variable in some way. Theano does not
# know off hand that the matrix func_of_W1 has shape (20, 10)
func_of_W1 = W1.dimshuffle(2, 0, 1).flatten(2).T

# source of error: dot product of 5x10 with 20x10
h1 = T.dot(x, func_of_W1)

# do more stuff
h2 = T.dot(h1, W2.T)

# compile and call the actual function
f = theano.function([x], h2)
f(numpy.random.rand(5, 10))
    运行上面的代码,生成下面的错误信息:

Traceback (most recent call last):
  File "test1.py", line 31, in <module>
    f(numpy.random.rand(5, 10))
  File "PATH_TO_THEANO/theano/compile/function_module.py", line 605, in __call__
    self.fn.thunks[self.fn.position_of_error])
  File "PATH_TO_THEANO/theano/compile/function_module.py", line 595, in __call__
    outputs = self.fn()
ValueError: Shape mismatch: x has 10 cols (and 5 rows) but y has 20 rows (and 10 cols)
Apply node that caused the error: Dot22(x, DimShuffle{1,0}.0)
Inputs types: [TensorType(float64, matrix), TensorType(float64, matrix)]
Inputs shapes: [(5, 10), (20, 10)]
Inputs strides: [(80, 8), (8, 160)]
Inputs scalar values: ['not scalar', 'not scalar']

Debugprint of the apply node:
Dot22 [@A] <TensorType(float64, matrix)> ''
 |x [@B] <TensorType(float64, matrix)>
 |DimShuffle{1,0} [@C] <TensorType(float64, matrix)> ''
   |Flatten{2} [@D] <TensorType(float64, matrix)> ''
     |DimShuffle{2,0,1} [@E] <TensorType(float64, 3D)> ''
       |W1 [@F] <TensorType(float64, 3D)>

HINT: Re-running with most Theano optimization disabled could give you a back-traces when this node was created. This can be done with by setting the Theano flags 'optimizer=fast_compile'. If that does not work, Theano optimization can be disabled with 'optimizer=None'.
    如果上面的信息还不够, 可以通过改变一些代码,从而让theano来揭示错误的准确来源。

# enable on-the-fly graph computations
theano.config.compute_test_value = 'warn'

...

# input which will be of shape (5, 10)
x  = T.matrix('x')
# provide Theano with a default test-value
x.tag.test_value = numpy.random.rand(5, 10)
    上面的代码中,我们将符号矩阵x 赋值一个特定的测试值。这允许theano按照之前定义的那样,动态的执行符号表达式(通过对每个op调用perform方法)。因此,可以在编译通道中更准确和更早的识别到错误的来源。例如,运行上面的代码得到下面的错误信息,正确的识别到了第24行。

Traceback (most recent call last):
  File "test2.py", line 24, in <module>
    h1 = T.dot(x, func_of_W1)
  File "PATH_TO_THEANO/theano/tensor/basic.py", line 4734, in dot
    return _dot(a, b)
  File "PATH_TO_THEANO/theano/gof/op.py", line 545, in __call__
    required = thunk()
  File "PATH_TO_THEANO/theano/gof/op.py", line 752, in rval
    r = p(n, [x[0] for x in i], o)
  File "PATH_TO_THEANO/theano/tensor/basic.py", line 4554, in perform
    z[0] = numpy.asarray(numpy.dot(x, y))
ValueError: matrices are not aligned

 compute_test_value 机制如下方式工作:

  • 当使用Theano的 constants 和 shared 变量的时候,不需要instrument它们。
  • 一个theano变量 (例如: dmatrixvector,等等) 应该通过属性 tag.test_value来赋值特定的测试值。
  • Theano 会自动instruments 中间的结果。所以,任何从x中得到的值会自动由tag.test_value引用。

compute_test_value 可以有以下的值:

  • off: 默认行为. 这时候调试机制是未激活的。
  • raise:动态计算测试值。任何变量都需要一个测试值,不过不需要用户来提供,这被认为是一个错误。会相应的抛出一个异常。
  • warn: Idem, 发出一个警告,而不是抛出异常。
  • ignore: 当一个变量没有测试值的时候,会静默的忽略掉中间测试值的计算。
note:该特性暂时不能与 Scan 兼容,而且也无法和那些没有实现perform方法的ops相兼容。

四、我如何在一个函数中输出中间值?

    Theano提供了一个‘Print’ 操作:

x = theano.tensor.dvector('x')

x_printed = theano.printing.Print('this is a very important value')(x)

f = theano.function([x], x * 5)
f_with_print = theano.function([x], x_printed * 5)

#this runs the graph without any printing
assert numpy.all( f([1, 2, 3]) == [5, 10, 15])

#this runs the graph with the message, and value printed
assert numpy.all( f_with_print([1, 2, 3]) == [5, 10, 15])
    因为 Theano 是以拓扑顺序来运行你的程序的,你没法准确的按照顺序来控制,这时候多个Print()是同时运行的。想要知道更详细的关于在哪里、什么时候、怎样计算的,查阅: “How do I Step through a Compiled Function?”.

warning:使用这个Print Theano 操作可以防止一些theano的优化。这也可以在稳定的优化的时候使用,所以如果你使用这个Print,然后有NaN,那么就试着移除它们来看看是否是它们导致的错误 。

五、我如何在编译前后输出一个graph

    Theano 提供两个函数 (theano.pp() 和 theano.printing.debugprint()) 来在编译的前后打印graph到终端上。这两个函数以不同的方式来打印表达式: pp() 更紧凑,而且更像数学; debugprint() 更详细 。Theano 同样提供 theano.printing.pydotprint() ,这会生成一副关于函数的png图片。

更详细的查阅: printing – Graph Printing and Symbolic Print Statement.

六、我编译的函数太慢了,怎么办?

    首先,确保你运行在 FAST_RUN 模式下。虽然 FAST_RUN 是默认情况下的模式,不过还是坚持要传递 mode='FAST_RUN' 给theano.function (或者 theano.make) 或者设置config.mode 为 FAST_RUN.

其次,尝试 Theano ProfileMode. 这会告诉你现在是哪个 Apply 节点和哪个ops在你的cpu周期上。 

提示:

  • 使用flags floatX=float32 来请求类型 float32 而不是 float64; 使用 Theano 构造函数matrix(),vector(),... 而不是 dmatrix(), dvector(),... 因为他们分别涉及到默认的类型 float32 和 float64.
  • 当你想要以相同的类型来将两个矩阵进行相乘的时候,记得以profile模式来检查在编译后的graph中没有Dot操作。当输入是矩阵而且有着相同的类型的时候,Dot会被优化成dot22。当然在使用floatX=float32 ,而且其中一个graph的输入是类型float64的时候也是这样。

七、我如何对一个编译后的函数进行step调试

    你可以使用 MonitorMode 来检查当函数被调用的时候每个节点的输入和输出。下面的代码就展示了如何打印所有的输入和输出:

import theano

def inspect_inputs(i, node, fn):
    print i, node, "input(s) value(s):", [input[0] for input in fn.inputs],

def inspect_outputs(i, node, fn):
    print "output(s) value(s):", [output[0] for output in fn.outputs]

x = theano.tensor.dscalar('x')
f = theano.function([x], [5 * x],
                    mode=theano.compile.MonitorMode(
                        pre_func=inspect_inputs,
                        post_func=inspect_outputs))
f(3)

# The code will print the following:
#   0 Elemwise{mul,no_inplace}(TensorConstant{5.0}, x) input(s) value(s): [array(5.0), array(3.0)] output(s) value(s): [array(15.0)]
    当在 MonitorMode的情况下,使用 inspect_inputs 和 inspect_outputs 这些函数。你应该看到 [可能很多] 打印的输出。每个 Apply 节点都会被打印出来,按照graph中的位置顺序,参数到函数 perform 或者 c_code 和计算得到的输出。不可否认,如果你使用的是大张量,这会有着超多的输出要读... 不过你可以选择增加逻辑来打印一部分信息,比如打印那些用到某种op的,在程序的某个位置,或者在输入或者输出上的一个具体的值。一个典型的例子就是检测什么时候NaN的值会被加到计算中,如下面代码:

import numpy

import theano

# This is the current suggested detect_nan implementation to
# show you how it work.  That way, you can modify it for your
# need.  If you want exactly this method, you can use
# ``theano.compile.monitormode.detect_nan`` that will always
# contain the current suggested version.

def detect_nan(i, node, fn):
    for output in fn.outputs:
        if (not isinstance(output[0], numpy.random.RandomState) and
            numpy.isnan(output[0]).any()):
            print '*** NaN detected ***'
            theano.printing.debugprint(node)
            print 'Inputs : %s' % [input[0] for input in fn.inputs]
            print 'Outputs: %s' % [output[0] for output in fn.outputs]
            break

x = theano.tensor.dscalar('x')
f = theano.function([x], [theano.tensor.log(x) * x],
                    mode=theano.compile.MonitorMode(
                        post_func=detect_nan))
f(0)  # log(0) * 0 = -inf * 0 = NaN

# The code above will print:
#   *** NaN detected ***
#   Elemwise{Composite{[mul(log(i0), i0)]}} [@A] ''
#    |x [@B]
#   Inputs : [array(0.0)]
#   Outputs: [array(nan)]
    为了帮助理解在你的graph中在发生的的事情,你可以禁用 local_elemwise_fusion 和所有的 inplace 优化。首先是速度优化,也就是会将逐元素操作融合到一起的优化。这会使的更难知道哪个具体的逐元素导致的问题。第二个优化就是会让某些ops的输出重写它们的输入。所以如果一个op生成一个坏的输出,你就没法看到在post_func函数中被重写之前的输入。为了禁用这些优化(0.6rc3之后的版本),如下定义MonitorMode:

mode = theano.compile.MonitorMode(post_func=detect_nan).excluding(
    'local_elemwise_fusion', 'inplace)
 f = theano.function([x], [theano.tensor.log(x) * x],
                     mode=mode)
note:Theano flags optimizer_includingoptimizer_excluding 和 optimizer_requiring 不会被 MonitorMode使用的,它们只会在default模式下使用。当你想要定义监视的部分的时候,你没法将 default 模式和MonitorMode一起使用。

    为了确保所有的节点的输入都是在调用到psto_func的时候可用的,你必须同样禁用垃圾回收。执行的节点垃圾回收那些theano函数不再需要的输入。这可以通过下面的flag来指定

allow_gc=False

八、我如何使用pdb

   在大部分情况下,你不是在交互模式下执行程序而是以python脚本的方式。在这种情况下,对python调试器的使用就变得十分的需要了,特别是当你的模型变得更加复杂的时候。中间的结果不需要有很清晰的名字,而且你会得到那些很那解读的异常,因为这是函数编译后的自然特性导致的:

考虑这个例子脚本 (“ex.py”):

import theano
import numpy
import theano.tensor as T

a = T.dmatrix('a')
b = T.dmatrix('b')

f = theano.function([a, b], [a * b])

# matrices chosen so dimensions are unsuitable for multiplication
mat1 = numpy.arange(12).reshape((3, 4))
mat2 = numpy.arange(25).reshape((5, 5))

f(mat1, mat2)
    这实际上如此的简单,而且调试也是如此的容易,不过这是为了图文讲解的目的。 正如矩阵没法逐元素相乘(不匹配的shapes),我们得到了下面的异常:

File "ex.py", line 14, in <module>
  f(mat1, mat2)
File "/u/username/Theano/theano/compile/function_module.py", line 451, in __call__
File "/u/username/Theano/theano/gof/link.py", line 271, in streamline_default_f
File "/u/username/Theano/theano/gof/link.py", line 267, in streamline_default_f
File "/u/username/Theano/theano/gof/cc.py", line 1049, in execute ValueError: ('Input dimension mis-match. (input[0].shape[0] = 3, input[1].shape[0] = 5)', Elemwise{mul,no_inplace}(a, b), Elemwise{mul,no_inplace}(a, b))

    调用的堆栈包含着一些有用的信息,从而可以追溯错误的来源。 首先是编译后的函数被调用的脚本– 不过如果你使用(不正确的参数化)预建立模块,错误也许来自这些模块中的ops,而不是这个脚本。最后一行告诉我们这个op引起了这个异常。一个“mul”涉及到变量“a”和“b”。不过这里假设我们替换了一个没有名字的中间值。 

    在了解了theano中graph结构的一些知识,我们可以使用python调试器来探索这个graph,然后我们就可以得到运行时的错误信息。特别是矩阵维度对指出错误的来源很有用。在打印出的结果中,会涉及到矩阵的4维度中的2个维度,不过因为例子的原因,我们需要其他的维度来指出错误。首先我们再次运行调试器模块,然后用“c”来运行该程序:

python -m pdb ex.py
> /u/username/experiments/doctmp1/ex.py(1)<module>()
-> import theano
(Pdb) c

    然后我们返回到上面错误的打印输出部分,不过解释器停留在了那个状态。有用的命令如下:

  • “up” 和 “down” (往下或往上移动这个调用堆栈),
  • “l” (在当前堆栈位置上打印该行周围的代码),
  • “p variable_name” (打印 ‘variable_name’的字符串解释),
  • “p dir(object_name)”, 使用python的 dir() 函数来打印一个对象的成员的列表。

    例如,键入 “up”,和一个简单的 “l” 会得到一个局部变量 “node”。 该 “node” 来自于计算graph中,所以通过跟随 “node.inputs”, “node.owner” 和 “node.outputs” 连接,就能探索这个graph。

    这个graph是纯符号的 (没有数据,只有抽象的符号操作)。为了得到实际参数的信息,你需要探索 “thunk” 对象,这是通过函数自身(一个“thunk”就是一个关于闭包的概念)来绑定了输入(和输出)的存储的 。这里,为了得到当前节点的第一个输入shape,你需要键入 “p thunk.inputs[0][0].shape”,这会打印出 “(3, 4)”.

九、Dumping 一个函数来帮助调试

    如果你读到这里了,那么就可能是你邮件到了我们的主列表,然后我们建议你读的这部分。这部分解释了如何dump所有的传到theano.function()的参数。这有助于帮助我们在编译的时候复制问题,然这并不要求你举一个自圆其说的例子。

    为了让这工作起来,我们需要导入graph中所有op的代码,所以如果你创建了你自己的op,我们需要这份代码。然而,我们不会unpickle它,我们已经有了来自theano和Pylearn2的所有Ops:

# Replace this line:
theano.function(...)
# with
theano.function_dump(filename, ...)
# Where filename is a string to a file that we will write to.

然后和我们说文件名。


参考资料:

[1]官网:http://deeplearning.net/software/theano/tutorial/shape_info.html


posted @ 2015-06-19 14:27  仙守  阅读(9782)  评论(0编辑  收藏  举报