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

时间:2023-03-09 03:01:45
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