* removed "testcase" param from verify_grad as it wasn't being used

* fixed a bug with verify_grad failing on the second iteration with inplace operation (missing p.copy()) * renamed make_restet and make_broadcast_restet to makeTester and makeBroadcastTestser (sorry OB. was about to document and I figured it would be clearer with the real names...)
上级 184dd72c
...@@ -222,3 +222,41 @@ Similarly, to provide a seed to numpy.random.RandomState, simply use: ...@@ -222,3 +222,41 @@ Similarly, to provide a seed to numpy.random.RandomState, simply use:
>>> rng = numpy.random.RandomState(unittest_tools.fetch_seed(1231)) >>> rng = numpy.random.RandomState(unittest_tools.fetch_seed(1231))
Note that the ability to change the seed from one nosetest to another, is incompatible with the method of hard-coding the baseline variables (against which we compare the theano outputs). These must then be determined "algorithmically". Although this represents more work, the test suite will be better because of it. Note that the ability to change the seed from one nosetest to another, is incompatible with the method of hard-coding the baseline variables (against which we compare the theano outputs). These must then be determined "algorithmically". Although this represents more work, the test suite will be better because of it.
Creating an Op UnitTest
=======================
A few tools have been developed to help automate the development of unitests
for Theano Ops.
Validating the Gradient
-----------------------
The ``verify_grad`` function can be used to validate that the ``grad``
function of your Op is properly implemented. ``verify_grad`` is based on the
Finite Difference Method where the derivative of function ``f`` at point ``x``
is approximated as:
.. math::
\frac{\partial{f}}{\partial{x}} = lim_{\Delta \rightarrow 0} \frac {f(x+\Delta) - f(x-\Delta)} {2\Delta}
``verify_grad`` performs the following steps:
* approximates the gradient numerically using the Finite Difference Method
* calculate the gradient using the symbolic expression provided in the ``grad`` function
* compares the two values. The tests passes if they are equal to within a certain tolerance.
>>> def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0.0001,
>>> mode=compile.Mode(optimizer=None, linker='c&py')):
>>> """
>>> Raises an Exception if the analytic gradient and numerical gradient exceeds a certain tolerance.
>>> :param testcase: (obsolete) a reference to the unittest object calling
>>> :param pt: the list of numpy.ndarrays to use as inputs to the op
>>> :param op: something that behaves like an Op instance with a single output
>>> (can be a python function combining multiple ops)
>>> :param testcase: the thing to call `fail` on if things go awry.
...@@ -20,7 +20,7 @@ from ..gof.python25 import partial ...@@ -20,7 +20,7 @@ from ..gof.python25 import partial
from .. import compile, printing from .. import compile, printing
from ..printing import pprint, Print from ..printing import pprint, Print
from ..tests import unittest_tools
### set up the external interface ### set up the external interface
from elemwise import Elemwise, DimShuffle, CAReduce, Sum from elemwise import Elemwise, DimShuffle, CAReduce, Sum
...@@ -2327,29 +2327,35 @@ class numeric_grad: ...@@ -2327,29 +2327,35 @@ class numeric_grad:
errs.append(numpy.max(numeric_grad.abs_rel_err(a,b))) errs.append(numpy.max(numeric_grad.abs_rel_err(a,b)))
return numpy.max(errs) return numpy.max(errs)
# TODO: remove testcase parameter as it is not used... and useless (forces you to
# run your testcases from a unittest.TestCase class = not necessary) def verify_grad(op, pt, n_tests=2, rng=None, eps=1.0e-7, tol=0.0001):
def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0.0001,
mode=compile.Mode(optimizer=None, linker='c&py')):
""" WRITEME """ WRITEME
testcase.failUnless(analytic gradient matches finite-diff gradient) Raises an Exception if the difference between the analytic gradient and
numerical gradient (computed through the Finite Difference Method) exceeds
the given tolerance.
:param pt: the list of numpy.ndarrays to use as inputs to the op
:param op: something that behaves like an Op instance with a single output :param op: something that behaves like an Op instance with a single output
(can be a python function combining multiple ops) (can be a python function combining multiple ops)
:param testcase: the thing to call `fail` on if things go awry. :param pt: the list of numpy.ndarrays to use as inputs to the op
:param n_tests: number of times to run the test
:param rng: random number generator from which to draw random samples
:param eps: stepsize used in the Finite Difference Method
:param tol: relative tolerance used as threshold for gradient comparison
""" """
pt = [numpy.array(p) for p in pt] pt = [numpy.array(p) for p in pt]
#print "PT", pt if rng is None:
rng = numpy.random
unittest_tools.seed_rng()
def function(inputs, output): def function(inputs, output):
f = compile.function(inputs, output, mode=mode, accept_inplace=True) f = compile.function(inputs, output, accept_inplace=True)
return f return f
for test_num in xrange(n_tests): for test_num in xrange(n_tests):
tensor_pt = [value(p.copy(), name='input %i'%i) for i,p in enumerate(pt)] tensor_pt = [value(p.copy(), name='input %i'%i) for i,p in enumerate(pt)]
#op can be either a function or an actual Op instance #op can be either a function or an actual Op instance
...@@ -2360,8 +2366,10 @@ def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0 ...@@ -2360,8 +2366,10 @@ def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0
# we could make loop over outputs making random projections R for each, # we could make loop over outputs making random projections R for each,
# but this doesn't handle the case where not all the outputs are # but this doesn't handle the case where not all the outputs are
# differentiable... so I leave this as TODO for now -JB. # differentiable... so I leave this as TODO for now -JB.
o_fn = function(tensor_pt, o_output) o_fn = function(tensor_pt, o_output)
o_fn_out = o_fn(*[p.copy() for p in pt]) o_fn_out = o_fn(*[p.copy() for p in pt])
random_projection = rng.rand(*o_fn_out.shape) random_projection = rng.rand(*o_fn_out.shape)
t_r = as_tensor_variable(random_projection) t_r = as_tensor_variable(random_projection)
...@@ -2375,15 +2383,13 @@ def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0 ...@@ -2375,15 +2383,13 @@ def verify_grad(testcase, op, pt, n_tests=1, rng=numpy.random, eps=1.0e-7, tol=0
grad_fn = function(tensor_pt, symbolic_grad) grad_fn = function(tensor_pt, symbolic_grad)
analytic_grad = grad_fn(*pt) analytic_grad = grad_fn(*[p.copy() for p in pt])
if not isinstance(analytic_grad, (list, tuple)): if not isinstance(analytic_grad, (list, tuple)):
analytic_grad = [analytic_grad] analytic_grad = [analytic_grad]
max_err = num_grad.max_err(analytic_grad) max_err = num_grad.max_err(analytic_grad)
if max_err > tol: if max_err > tol:
#print 'analytic grad', analytic_grad
#print 'numeric grad', num_grad.gf
raise Exception(verify_grad.E_grad, (max_err, tol)) raise Exception(verify_grad.E_grad, (max_err, tol))
verify_grad.E_grad = 'gradient error exceeded tolerance' verify_grad.E_grad = 'gradient error exceeded tolerance'
......
...@@ -14,13 +14,13 @@ class T_sigmoid(unittest.TestCase): ...@@ -14,13 +14,13 @@ class T_sigmoid(unittest.TestCase):
def setUp(self): def setUp(self):
unittest_tools.seed_rng() unittest_tools.seed_rng()
def test_elemwise(self): def test_elemwise(self):
TT.verify_grad(self, sigmoid, [numpy.random.rand(3,4)]) TT.verify_grad(sigmoid, [numpy.random.rand(3,4)])
class T_softplus(unittest.TestCase): class T_softplus(unittest.TestCase):
def setUp(self): def setUp(self):
unittest_tools.seed_rng() unittest_tools.seed_rng()
def test_elemwise(self): def test_elemwise(self):
TT.verify_grad(self, softplus, [numpy.random.rand(3,4)]) TT.verify_grad(softplus, [numpy.random.rand(3,4)])
class T_Softmax(unittest.TestCase): class T_Softmax(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -28,19 +28,19 @@ class T_Softmax(unittest.TestCase): ...@@ -28,19 +28,19 @@ class T_Softmax(unittest.TestCase):
def test0(self): def test0(self):
def f(a): def f(a):
return softmax(a)[:,0] return softmax(a)[:,0]
TT.verify_grad(self, f, [numpy.random.rand(3,4)]) TT.verify_grad(f, [numpy.random.rand(3,4)])
def test1(self): def test1(self):
def f(a): def f(a):
return softmax(a)[:,1] return softmax(a)[:,1]
TT.verify_grad(self, f, [numpy.random.rand(3,4)]) TT.verify_grad(f, [numpy.random.rand(3,4)])
def test2(self): def test2(self):
def f(a): def f(a):
return softmax(a)[:,2] return softmax(a)[:,2]
TT.verify_grad(self, f, [numpy.random.rand(3,4)]) TT.verify_grad(f, [numpy.random.rand(3,4)])
def test3(self): def test3(self):
def f(a): def f(a):
return softmax(a)[:,3] return softmax(a)[:,3]
TT.verify_grad(self, f, [numpy.random.rand(3,4)]) TT.verify_grad(f, [numpy.random.rand(3,4)])
class T_SoftmaxWithBias(unittest.TestCase): class T_SoftmaxWithBias(unittest.TestCase):
...@@ -49,22 +49,22 @@ class T_SoftmaxWithBias(unittest.TestCase): ...@@ -49,22 +49,22 @@ class T_SoftmaxWithBias(unittest.TestCase):
def test0(self): def test0(self):
def f(a, b): def f(a, b):
return softmax_with_bias(a, b)[:,0] return softmax_with_bias(a, b)[:,0]
TT.verify_grad(self, f, [numpy.random.rand(3,4), TT.verify_grad(f, [numpy.random.rand(3,4),
numpy.random.rand(4)]) numpy.random.rand(4)])
def test1(self): def test1(self):
def f(a, b): def f(a, b):
return softmax_with_bias(a, b)[:,1] return softmax_with_bias(a, b)[:,1]
TT.verify_grad(self, f, [numpy.random.rand(3,4), TT.verify_grad(f, [numpy.random.rand(3,4),
numpy.random.rand(4)]) numpy.random.rand(4)])
def test2(self): def test2(self):
def f(a, b): def f(a, b):
return softmax_with_bias(a, b)[:,2] return softmax_with_bias(a, b)[:,2]
TT.verify_grad(self, f, [numpy.random.rand(3,4), TT.verify_grad(f, [numpy.random.rand(3,4),
numpy.random.rand(4)]) numpy.random.rand(4)])
def test3(self): def test3(self):
def f(a, b): def f(a, b):
return softmax_with_bias(a, b)[:,3] return softmax_with_bias(a, b)[:,3]
TT.verify_grad(self, f, [numpy.random.rand(3,4), TT.verify_grad(f, [numpy.random.rand(3,4),
numpy.random.rand(4)]) numpy.random.rand(4)])
class T_CrossentropySoftmax1Hot(unittest.TestCase): class T_CrossentropySoftmax1Hot(unittest.TestCase):
...@@ -74,13 +74,13 @@ class T_CrossentropySoftmax1Hot(unittest.TestCase): ...@@ -74,13 +74,13 @@ class T_CrossentropySoftmax1Hot(unittest.TestCase):
y_idx = [0,1,3] y_idx = [0,1,3]
def f(a, b): def f(a, b):
return crossentropy_softmax_1hot_with_bias(a, b, y_idx)[0] return crossentropy_softmax_1hot_with_bias(a, b, y_idx)[0]
TT.verify_grad(self, f, [numpy.random.rand(3,4), TT.verify_grad(f, [numpy.random.rand(3,4),
numpy.random.rand(4)]) numpy.random.rand(4)])
def test1(self): def test1(self):
y_idx = [0,1,3] y_idx = [0,1,3]
def f(a): def f(a):
return crossentropy_softmax_1hot(a, y_idx)[0] return crossentropy_softmax_1hot(a, y_idx)[0]
TT.verify_grad(self, f, [numpy.random.rand(3,4)]) TT.verify_grad(f, [numpy.random.rand(3,4)])
class T_prepend(unittest.TestCase): class T_prepend(unittest.TestCase):
def setUp(self): def setUp(self):
......
...@@ -23,7 +23,7 @@ class T_XlogX(unittest.TestCase): ...@@ -23,7 +23,7 @@ class T_XlogX(unittest.TestCase):
# class Dummy(object): # class Dummy(object):
# def make_node(self, a): # def make_node(self, a):
# return [xlogx(a)[:,2]] # return [xlogx(a)[:,2]]
TT.verify_grad(self, xlogx, [numpy.random.rand(3,4)]) TT.verify_grad(xlogx, [numpy.random.rand(3,4)])
if __name__ == '__main__': if __name__ == '__main__':
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论