Theano調試技巧

分類:技術 時間:2017-01-13

Theano是最老牌的深度學習庫之一。它靈活的特點使其非常適合學術研究和快速實驗,但是它難以調試的問題也遭到過無數吐槽。其實Theano本身提供了很多輔助調試的手段,下面就介紹一些調試的技巧,讓Theano調試不再難。

以下的技巧和代碼均在Theano 0.8.2 上測試通過,不保證在更低的版本上也可以適用。

如何定位出錯位置

Theano的網絡在出錯的時候,往往會提供一些出錯信息。但是出錯信息往往非常模糊,讓人難以直接看出具體是哪一行代碼出現了問題。大家看下面的例子:

import theano
import theano.tensor as T
import numpy as np
x = T.vector()
y = T.vector()
z = x   x
z = z   y
f = theano.function([x, y], z)
f(
np.array([1,2],dtype=theano.config.floatX), np.array([3,4,5],dtype=theano.config.floatX))

將代碼保存到test.py文件中,在命令行中執行:

THEANO_FLAGS=quot;device=gpu0,floatX=float32quot; python test.py

輸出結果如下:

Traceback (most recent call last):
  File quot;test.pyquot;, line 10, in lt;modulegt;    print f(np.array([1,2],dtype='float32'), np.array([3,4,5],dtype='float32'))
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/function_module.pyquot;, line 871, in __call__
    storage_map=getattr(self.fn, 'storage_map', None))  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 314, in raise_with_op    reraise(exc_type, exc_value, exc_trace)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/function_module.pyquot;, line 859, in __call__
    outputs = self.fn()
ValueError: GpuElemwise. Input dimension mis-match. Input 1 (indices start at 0) has shape[0] == 2, but the output size on that axis is 3.
Apply node that caused the error: GpuElemwise{Composite{((i0   i1)   i0)}}[(0, 0)](GpuFromHost.0, GpuFromHost.0)
Toposort index: 2
Inputs types: [CudaNdarrayType(float32, vector), CudaNdarrayType(float32, vector)]
Inputs shapes: [(3,), (2,)]
Inputs strides: [(1,), (1,)]
Inputs values: [CudaNdarray([ 3.  4.  5.]), CudaNdarray([ 1.  2.])]
Outputs clients: [[HostFromGpu(GpuElemwise{Composite{((i0   i1)   i0)}}[(0, 0)].0)]]

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

比較有用的信息是:Input dimension mis-match,但是具體出問題在哪里,仍然讓人一頭霧水。因為Theano的計算圖進行了一些優化,導致出錯的時候難以與原始代碼對應起來。想解決這個也很簡單,就是關閉計算圖的優化功能。可以通過THEANO_FLAGS的optimizer,它的默認值是”fast_run”,代表最大程度的優化,我們平時一般就使用這個,但是如果想讓調試信息更詳細,我們就需要關閉一部分優化:fast_compile或者關閉全部優化:None,這里我們將optimizer設置成”None”,執行如下命令:

THEANO_FLAGS=quot;device=gpu0,floatX=float32,optimizer=Nonequot; python test.py

結果如下:

Traceback (most recent call last):
  File quot;test.pyquot;, line 10, in lt;modulegt;
    print f(np.array([1,2],dtype='float32'), np.array([3,4,5],dtype='float32'))
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/function_module.pyquot;, line 871, in __call__
    storage_map=getattr(self.fn, 'storage_map', None))
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 314, in raise_with_op
    reraise(exc_type, exc_value, exc_trace)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/function_module.pyquot;, line 859, 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}(lt;TensorType(float32, vector)gt;, lt;TensorType(float32, vector)gt;)
Toposort index: 0
Inputs types: [TensorType(float32, vector), TensorType(float32, vector)]
Inputs shapes: [(3,), (2,)]
Inputs strides: [(4,), (4,)]
Inputs values: [array([ 3.,  4.,  5.], dtype=float32), array([ 1.,  2.], dtype=float32)]
Outputs clients: [[Elemwise{add,no_inplace}(Elemwise{add,no_inplace}.0, lt;TensorType(float32, vector)gt;)]]

Backtrace when the node is created(use Theano flag traceback.limit=N to make it longer):
  File quot;test.pyquot;, line 7, in lt;modulegt;
    z = y   x

HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node.

可以看到,這次直接提示出錯的位置在代碼的第7行:z = y x,這個是不是方便很多了呢?

如何打印中間結果

下面分別介紹Test Values和Print兩種方法。

使用Test Values

我曾見過有人為了保證中間運算的實現沒有問題,先用numpy實現了一遍,檢查每一步運算結果符合預期以后,再移值改成Theano版的,其實大可不必這么折騰。Theano在0.4.0以后,加入了test values機制,簡單來說,就是在計算圖編譯之前,我們可以給symbolic提供一個具體的值,即test_value,這樣Theano就可以將這些數據,代入到symbolic表達式的計算過程中,從而完成計算過程的驗證,并可以打印出中間過程的運算結果。

大家看下面的例子:

import theano
import theano.tensor as T
import numpy as np
x = T.vector()
x.tag.test_value = http://www.tuicool.com/articles/np.array([1,2],dtype=theano.config.floatX)

y = T.vector()                                      y.tag.test_value = np.array([3,4,5],dtype=theano.config.floatX)
z = x   x
print z.tag.test_value
z = z   y
print z.tag.test_value
f = theano.function([x, y], z)

運行的時候,需要注意,如果需要使用test_value,那么需要設置一下compute_test_value的標記,有以下幾種

  • off: 關閉,建議在調試沒有問題以后,使用off,以提高程序速度。
  • ignore: test_value計算出錯,不會報錯
  • warn: test_value計算出錯,進行警告
  • raise: test_value計算出錯,會產出錯誤
  • pdb: test_value計算出錯,會進入pdb調試。pdb是python自帶的調試工具,在pdb里面可以單步查看各變量的值,甚至執行任意python代碼,非常強大,如果想看中間過程,又懶得打太多print,那么可以import pdb
    然后在你想設斷點的地方加上:pdb.set_trace(),后面可以用指令n單步,c繼續執行。更詳細的介紹可以參考這里: 26.2. pdb - The Python Debugger - Python 2.7.13 documentation

下面繼續回到test_value,我們將test_value值修改成warn,執行:

THEANO_FLAGS=quot;device=gpu0,floatX=float32,compute_test_value=http://www.tuicool.com/articles/warnquot; python test.py

結果如下:

[ 2.  4.]
log_thunk_trace: There was a problem executing an Op.
Traceback (most recent call last):
  File quot;test.pyquot;, line 12, in lt;modulegt;
    z = z   y
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/tensor/var.pyquot;, line 135, in __add__
    return theano.tensor.basic.add(self, other)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/op.pyquot;, line 668, in __call__
    required = thunk()
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/op.pyquot;, line 883, in rval
    fill_storage()
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/cc.pyquot;, line 1707, in __call__
    reraise(exc_type, exc_value, exc_trace)
  File quot;lt;stringgt;quot;, line 2, in reraiseValueError: Input dimension mis-match. (input[0].shape[0] = 2, input[1].shape[0] = 3)

可以看到,第一個z的值[2,4]被print了出來,同時在test_value的幫助下,錯誤信息還告訴我們在執行z = z y 這一行的時候有問題。因此test_value也可以起到,檢測哪一行出錯的功能。

小技巧:人工一個個構造test_value,實在太麻煩,因此可以考慮在訓練開始前,從訓練數據中隨機選一條,作為test_value,這樣還能輔助檢測,訓練數據有沒有問題。

使用Print

不過test_value對scan支持的不好,而如果網絡包含RNN的話,scan一般是不可或缺的。那么如何打印出scan在循環過程中的中間結果呢?這里我們可以使用

theano.printing.Print(),代碼如下:

import theano
import theano.tensor as T
import numpy as np
x = T.vector()
y = T.vector()
z = x   x
z = theano.printing.Print('z1')(z)
z = z   y
z = theano.printing.Print('z2')(z)
f = theano.function([x, y], z)
f(np.array([1,2],dtype=theano.config.floatX),np.array([1,2],dtype=theano.config.floatX))

執行:

THEANO_FLAGS=quot;device=gpu0,floatX=float32quot; python test.py

結果如下:

z1 __str__ = [ 2.  4.]
z2 __str__ = [ 3.  6.]

不過下面有幾點需要注意一下:

  • 因為theano是基于計算圖的,因此各變量在計算圖中被調用執行的順序,不一定和原代碼的順序一樣,因此變量Print出來的順序也是無法保證的。
  • Print方法,會比較嚴重的拖慢訓練的速度,因此最終用于訓練的代碼,最好把Print去除。
  • Print方法會阻止一些計算圖的優化,包括一些結果穩定性的優化,因此如果程序出現Nan問題,可以考慮把Print去除,再看看。

如何處理Nan

Nan是我們經常遇到的一個問題,我之前的文章: 深度學習網絡調試技巧 提到了如何處理Nan問題,其中最重要的步驟,是確定Nan最開始出現的位置。

一個比較暴力的方法,是打印出變量的中間結果,看看Nan是從哪里開始的,不過這樣工作量有點太大了。所以這里介紹另外一個比較省事的方法:NanGuardMode。NanGuardMode會監測指定的function,是否在計算過程中出現nan,inf。如果出現,會立刻報錯,這時配合前面提到的optimizer=None,我們就可以直接定位到,具體是哪一行代碼最先出現了Nan問題。代碼如下:

import theano
import theano.tensor as T
import numpy as np
from theano.compile.nanguardmode import NanGuardMode
x = T.matrix()
w = theano.shared(np.random.randn(5, 7).astype(theano.config.floatX))
y = T.dot(x, w)
fun = theano.function(
            [x], y,
mode=NanGuardMode(nan_is_error=True,inf_is_error=True, big_is_error=True)
)
infa = np.tile(
            (np.asarray(100.) ** 1000000).astype(theano.config.floatX), (3, 5))
fun(infa)

執行:

THEANO_FLAGS=quot;device=gpu0,floatX=float32,optimizer=Nonequot; python test.py

結果如下:

Traceback (most recent call last):
  File quot;test.pyquot;, line 12, in lt;modulegt;
    f(np.array([1,2],dtype='float32'),np.array([0,0],dtype='float32'))
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/function_module.pyquot;, line 859, in __call__
    outputs = self.fn()
Backtrace when the node is created(use Theano flag traceback.limit=N to make it longer):
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 1014, in f
    raise_with_op(node, *thunks)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 314, in raise_with_op
    reraise(exc_type, exc_value, exc_trace)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 1012, in f
    wrapper(i, node, *thunks)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/nanguardmode.pyquot;, line 307, in nan_check
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/gof/link.pyquot;, line 1012, in f
    wrapper(i, node, *thunks)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/nanguardmode.pyquot;, line 302, in nan_check
    do_check_on(x[0], node, fn, True)
  File quot;/home/wangzhe/anaconda2/lib/python2.7/site-packages/theano/compile/nanguardmode.pyquot;, line 272, in do_check_on
    raise AssertionError(msg)
AssertionError: Inf detected
Big value detected
NanGuardMode found an error in an input of this node.
Node:
dot(lt;TensorType(float32, matrix)gt;, HostFromGpu.0)
The input variable that cause problem:
dot [id A] ''   
 |lt;TensorType(float32, matrix)gt; [id B]
 |HostFromGpu [id C] ''   
   |lt;CudaNdarrayType(float32, matrix)gt; [id D]

Apply node that caused the error: dot(lt;TensorType(float32, matrix)gt;, HostFromGpu.0)
Toposort index: 1
Inputs types: [TensorType(float32, matrix), TensorType(float32, matrix)]
Inputs shapes: [(3, 5), (5, 7)]
Inputs strides: [(20, 4), (28, 4)]
Inputs values: ['not shown', 'not shown']
Outputs clients: [['output']]

Backtrace when the node is created(use Theano flag traceback.limit=N to make it longer):
  File quot;test.pyquot;, line 8, in lt;modulegt;
    y = T.dot(x, w)

HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node.

可以看到,是y = T.dot(x, w)這一行,產生的Nan.

其他

上面的幾個技巧,相信可以解決大部分Theano調試中遇到的問題. 同時我們在用Theano實現一些網絡結構,例如LSTM的時候,除了直接參考論文之外,這里強烈推薦參考keras進行實現。keras是一個更頂層的庫,同時支持Theano和Tensorflow作為后臺,里面大部分模型的實現都很可靠,可以學習和參考。

參考資料


Tags: Theano

文章來源:https://zhuanlan.zhihu.com/p/24857032


ads
ads

相關文章
ads

相關文章

ad