提交 8e9592b7 authored 作者: James Bergstra's avatar James Bergstra

merge

...@@ -242,6 +242,22 @@ class PureType(object): ...@@ -242,6 +242,22 @@ class PureType(object):
r.tag.trace = traceback.extract_stack()[:-1] r.tag.trace = traceback.extract_stack()[:-1]
return r return r
def values_eq_enough(self, a, b):
"""Return True if a and b can be considered equal as Op outputs, else False.
:param a: a potential value for a Result of this Type.
:param b: a potential value for a Result of this Type.
:rtype: Bool
This function is used by theano debugging tools to decide whether two values are
equivalent, admitting a certain amount of numerical instability. For example,
for floating-point numbers this function should be an approximate comparison.
"""
return (a == b)
_nothing = """ _nothing = """
""" """
......
""" Provides `OptCheck` """ Provides `DebugMode`
""" """
...@@ -25,6 +25,76 @@ from ..compile.function_module import (convert_function_input, ...@@ -25,6 +25,76 @@ from ..compile.function_module import (convert_function_input,
SymbolicOutput, SymbolicOutput,
Supervisor) Supervisor)
class DebugModeError(Exception):
pass
class BadClinkerOutput(DebugModeError):
"""Exception: a c implementation and python implementation don't agree"""
r = None
"""TODO"""
a = None
"""TODO"""
b = None
"""TODO"""
def __init__(self, r, a, b):
"""Initialize members"""
super(BadClinkerOutput, self).__init__()
self.r = r
self.a = a
self.b = b
class BadOptimization(DebugModeError):
"""Exception: some result and its substitute take different runtime values."""
new_r = None
"""TODO"""
r_val = None
"""TODO"""
new_r_val = None
"""TODO"""
reasons = []
"""TODO"""
snapshots = []
"""TODO"""
def __init__(self, new_r, r_val, new_r_val, reasons, snapshots):
"""Initialize members"""
super(BadOptimization, self).__init__()
self.new_r = new_r
self.r_val = r_val
self.new_r_val = new_r_val
self.reasons = reasons
self.snapshots = snapshots
#def __str__(self):
#return self.str_diagnostic() #debatable...
def str_diagnostic(self):
"""TODO: what does this mean? How to interpret? """
sio = StringIO()
print >> sio, " Result:", id(self.new_r), self.new_r
print >> sio, " Op", self.new_r.owner
print >> sio, " Value Type:", type(self.new_r_val)
print >> sio, " Old Value: ", self.r_val
print >> sio, " Value: ", self.new_r_val
print >> sio, " Reason: ", [(str(reason), id(old_r)) for reason, old_r in self.reasons[self.new_r]]
print >> sio, " Snapshots:"
for s in self.snapshots[self.new_r]:
print >> sio, " BEFORE"
print >> sio, s[1]
print >> sio, " AFTER"
print >> sio, s[2]
return sio.getvalue()
def debugprint(a, prefix='', depth=-1, done=None, file=sys.stdout): def debugprint(a, prefix='', depth=-1, done=None, file=sys.stdout):
if depth==0: if depth==0:
return return
...@@ -194,7 +264,21 @@ def optcheck_env(input_specs, output_specs, accept_inplace = False): ...@@ -194,7 +264,21 @@ def optcheck_env(input_specs, output_specs, accept_inplace = False):
env.extend(Supervisor(input for spec, input in zip(input_specs, inputs) if not (spec.mutable or (hasattr(env, 'destroyers') and env.destroyers(input))))) env.extend(Supervisor(input for spec, input in zip(input_specs, inputs) if not (spec.mutable or (hasattr(env, 'destroyers') and env.destroyers(input)))))
return env, map(SymbolicOutput, updates), equivalence_tracker return env, map(SymbolicOutput, updates), equivalence_tracker
class OptCheckLinker(OpWiseCLinker):
class DebugModeLinker(gof.link.LocalLinker):
def __init__(self, maker):
super(gof.LocalLinker, self).__init__()
self.env = None
self.maker = maker
def accept(self, env, no_recycling = []):
if self.env is not None and self.env is not env:
assert type(self) is DebugModeLinker
return type(self)(self.env, self.maker).accept(env, no_recycling)
self.env = env
self.no_recycling = no_recycling
return self
def make_all(self, profiler = None, input_storage = None, output_storage = None): def make_all(self, profiler = None, input_storage = None, output_storage = None):
env = self.env env = self.env
#order = env.toposort() #order = env.toposort()
...@@ -207,19 +291,20 @@ class OptCheckLinker(OpWiseCLinker): ...@@ -207,19 +291,20 @@ class OptCheckLinker(OpWiseCLinker):
order_outputs.reverse() order_outputs.reverse()
order = graph.io_toposort(env.inputs, order_outputs) order = graph.io_toposort(env.inputs, order_outputs)
no_recycling = self.no_recycling no_recycling = self.no_recycling
input_storage, output_storage, storage_map = link.map_storage(env, order, input_storage, output_storage) input_storage, output_storage, storage_map = link.map_storage(env, order, input_storage, output_storage)
thunks = [] thunks_py = [] #python thunks
thunks_c = [] #c thunks
for node in order: for node in order:
node_input_storage = [storage_map[r] for r in node.inputs] node_input_storage = [storage_map[r] for r in node.inputs]
node_output_storage = [storage_map[r] for r in node.outputs] node_output_storage = [storage_map[r] for r in node.outputs]
try: try:
raise NotImplementedError('need to copy destroyed inputs') if not self.maker.mode.check_c_code:
raise utils.AbstractFunctionError()
e = Env(*graph.clone(node.inputs, node.outputs)) e = Env(*graph.clone(node.inputs, node.outputs))
e.toposort = lambda: e.nodes e.toposort = lambda: e.nodes #WARNING: STOCHASTIC ORDER
if any(isinstance(input, graph.Value) for input in node.inputs): if any(isinstance(input, graph.Value) for input in node.inputs):
desc = None desc = None
...@@ -248,18 +333,18 @@ class OptCheckLinker(OpWiseCLinker): ...@@ -248,18 +333,18 @@ class OptCheckLinker(OpWiseCLinker):
output_storage = node_output_storage) output_storage = node_output_storage)
thunk.inputs = node_input_storage thunk.inputs = node_input_storage
thunk.outputs = node_output_storage thunk.outputs = node_output_storage
thunks.append(thunk) thunks_c.append(thunk)
except (NotImplementedError, utils.AbstractFunctionError): except utils.AbstractFunctionError:
if self.fallback_on_perform: thunks_c.append(None)
p = node.op.perform
thunk = (lambda p = p, i = node_input_storage, o = node_output_storage, n =
node: p(n, [copy.copy(x[0]) for x in i], o)) p = node.op.perform
thunk.inputs = node_input_storage thunk = (lambda p = p, i = node_input_storage, o = node_output_storage, n =
thunk.outputs = node_output_storage node: p(n, [x[0] for x in i], o))
thunk.perform = p thunk.inputs = node_input_storage
thunks.append(thunk) thunk.outputs = node_output_storage
else: thunk.perform = p
raise thunks_py.append(thunk)
if no_recycling is True: if no_recycling is True:
no_recycling = storage_map.values() no_recycling = storage_map.values()
...@@ -267,97 +352,102 @@ class OptCheckLinker(OpWiseCLinker): ...@@ -267,97 +352,102 @@ class OptCheckLinker(OpWiseCLinker):
else: else:
no_recycling = [storage_map[r] for r in no_recycling if r not in env.inputs] no_recycling = [storage_map[r] for r in no_recycling if r not in env.inputs]
#####
# This is the function that runs when you evaluate the graph
#####
def f(): def f():
for x in no_recycling: for x in no_recycling:
x[0] = None x[0] = None
try:
equiv_vals = {}
problematic = set()
r_vals = {}
assert len(thunks) == len(order)
for r in env.inputs: equiv_vals = {}
problematic = set()
# r_vals are the true values associated with each result in the graph
# they should not change during the evaluation of this function, even when the
# graph has destructive ops in it
#
# This dictionary is used to populate the storage_map as necessary
r_vals = {}
assert len(thunks_py) == len(order)
#put the initial values into the r_vals
for r in storage_map:
if storage_map[r][0] is not None:
r_vals[r] = copy.copy(storage_map[r][0]) r_vals[r] = copy.copy(storage_map[r][0])
storage_map[r][0] = None
try:
# compute the value of all results # compute the value of all results
for i, (thunk, node) in enumerate(zip(thunks, order)): for i, (thunk_py, thunk_c, node) in enumerate(zip(thunks_py, thunks_c, order)):
thunk()
#put a copy of each input into the storage_map
for r in node.inputs:
storage_map[r][0] = copy.copy(r_vals[r])
thunk_py()
#retrieve a copy of each output from the storage_map
for r in node.outputs: for r in node.outputs:
assert r not in r_vals if r in r_vals:
this_r_val = copy.copy(storage_map[r][0]) # r has been constant-folded
r_vals[r] = this_r_val if not r.type.values_eq_enough(r_vals[r], storage_map[r][0]):
raise Exception('BadConstantFold', (r, r_vals[r],
# iterate over results looking for values that don't match the values of the storage_map[r][0])) #TODO: make a proper exception class for this
# results they replaced. This is the sign of a broken optimization.
for i, (thunk, node) in enumerate(zip(thunks, order)):
for new_r in node.outputs:
for reason, r in env.equivalence_tracker.reasons[new_r]:
problem = False
#check if the value for new_r doesn't match the value for r
new_r_val = r_vals[new_r]
r_val = r_vals[r]
if type(new_r_val) != type(r_val):
problem = True
elif type(new_r_val) is numpy.ndarray:
if not numpy.allclose(new_r_val, r_val):
problem = True
else:
print >> sys.stderr, 'WARNING: OptCheck comparison of', type(new_r_val), 'NotImplementedError'
if problem:
print "OPTCHECK FAILURE"
print " Result:", id(new_r), new_r
print " Op", new_r.owner
print " Value Type:", type(new_r_val)
print " Old Value: ", r_val
print " Value: ", new_r_val
print " Reason: ", [(str(reason), id(old_r)) for reason, old_r in env.equivalence_tracker.reasons[new_r]]
print " Snapshots:"
for s in env.equivalence_tracker.snapshots[new_r]:
print " BEFORE"
print s[1]
print " AFTER"
print s[2]
print ""
# There is no point in continuing to check for more problems,
# because the incorrect result detected here will cause
# subsequent outputs to be incorrect.
raise Exception("OptCheckFailure")
if 0: #OLD CODE
#print out the summary of the first problematic equivalence group
min_member = []
for problem_r in problematic:
problem_r_set = env.equivalence_tracker.equiv[problem_r]
for i, n in enumerate(order):
if problem_r_set.intersection(n.outputs):
break
min_member.append((i, problem_r_set))
min_member.sort()
problematic_set = min_member[0][1]
else:
r_vals[r] = copy.copy(storage_map[r][0])
if thunk_c:
for r in node.outputs:
storage_map[r][0] = None #clear the storage_map for the thunk_c
if 0:
# TODO: check that Op didn't change any inputs that it wasn't allowed to
# (Hint: use the destroy_map attribute)
raise NotImplementedError()
else:
for r in node.inputs:
storage_map[r][0] = copy.copy(r_vals[r])
thunk_c()
for r in node.outputs:
# compares the version from thunk_py (in r_vals)
# to the version produced by thunk_c (in storage_map)
if not r.type.values_eq_enough(r_vals[r], storage_map[r][0]):
raise BadClinkerOutput(r, r_vals[r], storage_map[r][0])
except: except:
raise_with_op(node) raise_with_op(node)
f.allow_gc = self.allow_gc # iterate over results looking for values that don't match the values of the
# results they replaced. This is the sign of a broken optimization.
for i, node in enumerate(order):
for new_r in node.outputs:
for reason, r in env.equivalence_tracker.reasons[new_r]:
problem = False
#check if the value for new_r doesn't match the value for r
new_r_val = r_vals[new_r]
r_val = r_vals[r]
assert r.type == new_r.type
if not r.type.values_eq_enough(r_val, new_r_val):
raise BadOptimization(new_r, r_val, new_r_val,
env.equivalence_tracker.reasons,
env.equivalence_tracker.snapshots)
f.allow_gc = True
return f, [link.Container(input, storage) for input, storage in zip(env.inputs, input_storage)], \ return f, [link.Container(input, storage) for input, storage in zip(env.inputs, input_storage)], \
[link.Container(output, storage, True) for output, storage in zip(env.outputs, output_storage)], \ [link.Container(output, storage, True) for output, storage in zip(env.outputs, output_storage)], \
thunks, order thunks_py, order
NODEFAULT = ['NODEFAULT'] NODEFAULT = ['NODEFAULT']
class OptCheckFunctionMaker(FunctionMaker): class DebugModeFunctionMaker(FunctionMaker): #inheritance buys a few helper functions
verbose = 0
"""Verbosity level of compile-time and run-time checks. (Default 0: silent)"""
def __init__(self, inputs, outputs, optimizer,
chances_for_optimizer_to_screw_up = 10, def __init__(self, inputs, outputs, optimizer, mode,
accept_inplace = False, accept_inplace = False,
function_builder = Function): function_builder = Function):
""" """
...@@ -372,7 +462,6 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -372,7 +462,6 @@ class OptCheckFunctionMaker(FunctionMaker):
in the graph from the inputs to the outputs in the graph from the inputs to the outputs
""" """
# Handle the case where inputs and/or outputs is a single Result (not in a list) # Handle the case where inputs and/or outputs is a single Result (not in a list)
unpack_single = False unpack_single = False
if not isinstance(outputs, (list, tuple)): if not isinstance(outputs, (list, tuple)):
...@@ -388,7 +477,7 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -388,7 +477,7 @@ class OptCheckFunctionMaker(FunctionMaker):
expanded_inputs = reduce(list.__add__, [list(z) for x, y, z in indices], []) expanded_inputs = reduce(list.__add__, [list(z) for x, y, z in indices], [])
# make the env # make the env
for i in xrange(chances_for_optimizer_to_screw_up): for i in xrange(mode.stability_patience):
env, additional_outputs, equivalence_tracker = optcheck_env(expanded_inputs, outputs, accept_inplace) env, additional_outputs, equivalence_tracker = optcheck_env(expanded_inputs, outputs, accept_inplace)
env.equivalence_tracker = equivalence_tracker env.equivalence_tracker = equivalence_tracker
# optimize the env # optimize the env
...@@ -410,7 +499,8 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -410,7 +499,8 @@ class OptCheckFunctionMaker(FunctionMaker):
sys.exit(1) sys.exit(1)
break break
else: else:
print >> sys.stdout, "OPTCHECK: optimization", i, "of", len(li), "events was stable." if self.verbose:
print >> sys.stdout, "OPTCHECK: optimization", i, "of", len(li), "events was stable."
else: else:
env0 = env env0 = env
...@@ -419,7 +509,7 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -419,7 +509,7 @@ class OptCheckFunctionMaker(FunctionMaker):
self.env = env self.env = env
#equivalence_tracker.printstuff() #equivalence_tracker.printstuff()
linker = OptCheckLinker() linker = DebugModeLinker(self)
#the 'no_borrow' outputs are the ones for which that we can't return the internal storage pointer. #the 'no_borrow' outputs are the ones for which that we can't return the internal storage pointer.
...@@ -436,6 +526,7 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -436,6 +526,7 @@ class OptCheckFunctionMaker(FunctionMaker):
self.unpack_single = unpack_single self.unpack_single = unpack_single
self.accept_inplace = accept_inplace self.accept_inplace = accept_inplace
self.function_builder = function_builder self.function_builder = function_builder
self.mode = mode
def create(self, defaults = None, trustme = False): def create(self, defaults = None, trustme = False):
""" """
...@@ -531,7 +622,7 @@ class OptCheckFunctionMaker(FunctionMaker): ...@@ -531,7 +622,7 @@ class OptCheckFunctionMaker(FunctionMaker):
fn = self.function_builder(_fn, _i, _o, self.indices, self.outputs, defaults, self.unpack_single, self) fn = self.function_builder(_fn, _i, _o, self.indices, self.outputs, defaults, self.unpack_single, self)
return fn return fn
class OptCheck(Mode): class DebugMode(Mode):
"""Evaluation Mode that detects optimization errors. """Evaluation Mode that detects optimization errors.
A basic premise of how theano works is that every node that is replaced during optimization should compute the same thing as its replacement. A basic premise of how theano works is that every node that is replaced during optimization should compute the same thing as its replacement.
...@@ -547,14 +638,16 @@ class OptCheck(Mode): ...@@ -547,14 +638,16 @@ class OptCheck(Mode):
# function_module.function # function_module.function
def function_maker(self, i,o,m, *args, **kwargs): def function_maker(self, i,o,m, *args, **kwargs):
assert m is self assert m is self
return OptCheckFunctionMaker(i, o, self.optimizer, return DebugModeFunctionMaker(i, o, self.optimizer, self, *args, **kwargs)
chances_for_optimizer_to_screw_up=self.stability_patience, def __init__(self,
*args, **kwargs) optimizer='fast_run',
def __init__(self, optimizer='fast_run', stability_patience=10): stability_patience=10,
super(OptCheck, self).__init__( check_c_code=True):
super(DebugMode, self).__init__(
optimizer=optimizer, optimizer=optimizer,
linker=OptCheckLinker) linker=DebugModeLinker)
self.stability_patience = stability_patience self.stability_patience = stability_patience
self.check_c_code = check_c_code
import numpy
import scipy.sparse
from theano import gof
import theano.sparse
import theano import theano
import theano.tensor import theano.tensor
import debugmode import debugmode
import theano.compile
def test0(): def test0():
x = theano.tensor.dvector() x = theano.tensor.dvector()
f = theano.function([x], (2.*x + 7) / 2., mode=debugmode.OptCheck()) f = theano.function([x], (2.*x + 7) / 2., mode=debugmode.DebugMode())
print f([1,2]) print f([1,2])
class BROKEN_ON_PURPOSE_StructuredDotCSC(gof.Op):
def __init__(self, py_offset):
gof.Op.__init__(self)
self.py_offset = py_offset
def __eq__(self, other):
return type(self) == type(other) and (self.py_offset == other.py_offset)
def __hash__(self):
return 29834 ^ hash(type(self)) ^ hash(self.py_offset)
def make_node(self, a_val, a_ind, a_ptr, a_nrows, b):
a_nrows = theano.tensor.as_tensor(a_nrows)
assert a_val.type.dtype == b.type.dtype
r = gof.Apply(self, [a_val, a_ind, a_ptr, a_nrows, b],
[theano.tensor.tensor(a_val.type.dtype, (False, False))])
return r
def perform(self, node, (a_val, a_ind, a_ptr, a_nrows, b), (out,)):
a = scipy.sparse.csc_matrix((a_val, a_ind, a_ptr),
(a_nrows, b.shape[0]),
copy = False)
# TODO: todense() is automatic in 0.7.0, just remove the following line:
# out[0] = numpy.asarray(a.dot(b).todense())
out[0] = a.dot(b)+0.5 if self.py_offset else a.dot(b) #ERROR TO ADD THIS CRAPPY OFFSET
#assert _is_dense(out[0])
def c_code(self, node, name, (a_val, a_ind, a_ptr, a_nrows, b), (z,), sub):
return """
if (%(a_val)s->nd != 1) {PyErr_SetString(PyExc_NotImplementedError, "rank(a_val) != 1"); %(fail)s;}
if (%(a_ind)s->nd != 1) {PyErr_SetString(PyExc_NotImplementedError, "rank(a_ind) != 1"); %(fail)s;}
if (%(a_ptr)s->nd != 1) {PyErr_SetString(PyExc_NotImplementedError, "rank(a_ptr) != 1"); %(fail)s;}
if (%(a_nrows)s->nd != 0) {PyErr_SetString(PyExc_NotImplementedError, "rank(nrows) != 0"); %(fail)s;}
if (%(b)s->nd != 2) {PyErr_SetString(PyExc_NotImplementedError, "rank(b) != 2"); %(fail)s;}
if (%(a_val)s->descr->type_num != PyArray_DOUBLE)
{PyErr_SetString(PyExc_NotImplementedError, "a_val dtype not NPY_DOUBLE"); %(fail)s;}
if (%(a_ind)s->descr->type_num != PyArray_INT32) {
PyErr_SetString(PyExc_NotImplementedError, "a_ind dtype not INT32"); %(fail)s;}
if (%(a_ptr)s->descr->type_num != PyArray_INT32)
{PyErr_SetString(PyExc_NotImplementedError, "a_ptr dtype not INT32"); %(fail)s;}
if (%(a_nrows)s->descr->type_num != PyArray_INT32)
{PyErr_SetString(PyExc_NotImplementedError, "a_nrows dtype not INT32"); %(fail)s;}
if (%(b)s->descr->type_num != PyArray_DOUBLE)
{PyErr_SetString(PyExc_NotImplementedError, "b's dtype not NPY_DOUBLE"); %(fail)s;}
if (%(a_val)s->dimensions[0] != %(a_ind)s->dimensions[0])
{PyErr_SetString(PyExc_NotImplementedError, "a_val and a_ind have different lengths"); %(fail)s;}
if (%(a_ptr)s->dimensions[0] != %(b)s->dimensions[0]+1)
{PyErr_SetString(PyExc_NotImplementedError, "a's number of columns doesn't match b's rows"); %(fail)s;}
if ((!%(z)s)
|| (%(z)s->dimensions[0] != ((npy_int32 *)%(a_nrows)s->data)[0])
|| (%(z)s->dimensions[1] != %(b)s->dimensions[1])
)
{
if (%(z)s) Py_DECREF(%(z)s);
npy_intp dims[] = {0,0};
dims[0] = ((npy_int32 *)%(a_nrows)s->data)[0];
dims[1] = %(b)s->dimensions[1];
%(z)s = (PyArrayObject*) PyArray_SimpleNew(2, dims, %(b)s->descr->type_num);
}
{
//the output array has size M x N
npy_intp M = %(z)s->dimensions[0];
npy_intp N = %(z)s->dimensions[1];
npy_intp K = %(b)s->dimensions[0];
npy_intp Szm = %(z)s->strides[0] / %(z)s->descr->elsize;
npy_intp Szn = %(z)s->strides[1] / %(z)s->descr->elsize;
//npy_intp Sbm = %(b)s->strides[0] / %(b)s->descr->elsize;
npy_intp Sbn = %(b)s->strides[1] / %(b)s->descr->elsize;
npy_intp Sval = %(a_val)s->strides[0] / %(a_val)s->descr->elsize;
npy_intp Sind = %(a_ind)s->strides[0] / %(a_ind)s->descr->elsize;
npy_intp Sptr = %(a_ptr)s->strides[0] / %(a_ptr)s->descr->elsize;
npy_double * __restrict__ Dz = (npy_double*)%(z)s->data;
//const npy_double * __restrict__ Db = (npy_double*)%(b)s->data;
const npy_double * __restrict__ Dval = (npy_double*)%(a_val)s->data;
const npy_int32 * __restrict__ Dind = (npy_int32*)%(a_ind)s->data;
const npy_int32 * __restrict__ Dptr = (npy_int32*)%(a_ptr)s->data;
//npy_intp nnz = %(a_ind)s->dimensions[0];
//clear the output array
for (npy_intp m = 0; m < M; ++m)
{
for (npy_intp n = 0; n < N; ++n)
{
//Dz[m*Szm + n*Szn] = 0.0;
Dz[m*Szm + n*Szn] = 0.5; //here is the py_offset amount
}
}
//iterate over the sparse array, making the most of an entry wherever we find it.
//
// Normal matrix matrix multiply:
// for m
// for n
// for k
// z[m,n] += a[m,k] * b[k,n]
// Here instead:
// for k
// for m (sparse)
// for n
// z[m,n] += a[m,k] * b[k,n]
for (npy_int32 k = 0; k < K; ++k)
{
const npy_double * __restrict__ bk = (double *)(%(b)s->data + %(b)s->strides[0] * k);
for (npy_int32 m_idx = Dptr[k * Sptr]; m_idx < Dptr[(k+1) * Sptr]; ++m_idx)
{
npy_int32 m = Dind[m_idx * Sind];
const double Amk = Dval[m_idx * Sval];
npy_double * __restrict__ zm = (npy_double *)(%(z)s->data + %(z)s->strides[0] * m);
if (m >= %(z)s->dimensions[0])
{PyErr_SetString(PyExc_NotImplementedError, "illegal row index in a"); %(fail)s;}
for(npy_int32 n = 0; n < N; ++n)
{
zm[n*Szn] += Amk * bk[n*Sbn];
}
}
}
}
"""% dict(locals(), **sub)
# inconsistent is a invalid op, whose perform and c_code do not match
inconsistent = BROKEN_ON_PURPOSE_StructuredDotCSC(False)
# off_by_half is a good op, that is different from theano.sparse.sd_csc
off_by_half = BROKEN_ON_PURPOSE_StructuredDotCSC(True)
def test_badclinkeroutput():
vals = theano.tensor.dvector()
inds = theano.tensor.ivector()
ptrs = theano.tensor.ivector()
nrows = theano.tensor.iscalar()
b = theano.tensor.dmatrix()
f_good = theano.function([vals, inds, ptrs, nrows, b],
theano.sparse.StructuredDotCSC()(vals, inds, ptrs, nrows, b),
mode=debugmode.DebugMode(check_c_code=True))
f_inconsistent = theano.function([vals, inds, ptrs, nrows, b],
inconsistent(vals, inds, ptrs, nrows, b),
mode=debugmode.DebugMode(check_c_code=True))
#this should evaluate with no error
rval_good = f_good([1.0, 2.0, 3.0],
[0,1,2],
[0,1,2,3],
3,
numpy.asarray([[0.,1.,2.],[3.,4.,5.],[6.,7.,8.]]))
try:
rval = f_inconsistent([1.0, 2.0, 3.0],
[0,1,2],
[0,1,2,3],
3,
numpy.asarray([[0.,1.,2.],[3.,4.,5.],[6.,7.,8.]]))
except debugmode.BadClinkerOutput, e:
print repr(e)
assert e.r.owner.op is inconsistent
return #TEST PASS
assert False #an error should have been detected
def test_badoptimization():
@gof.local_optimizer([theano.sparse.sd_csc])
def insert_broken_csc(node):
if node.op == theano.sparse.sd_csc:
return [off_by_half(*node.inputs)]
return False
edb = gof.EquilibriumDB()
edb.register('insert_broken_csc', insert_broken_csc, 'all')
opt = edb.query('+all')
vals = theano.tensor.dvector()
inds = theano.tensor.ivector()
ptrs = theano.tensor.ivector()
nrows = theano.tensor.iscalar()
b = theano.tensor.dmatrix()
f = theano.function([vals, inds, ptrs, nrows, b],
theano.sparse.sd_csc(vals, inds, ptrs, nrows, b),
mode=debugmode.DebugMode(optimizer=opt, check_c_code=True))
try:
rval = f([1.0, 2.0, 3.0],
[0,1,2],
[0,1,2,3],
3,
numpy.asarray([[0.,1.,2.],[3.,4.,5.],[6.,7.,8.]]))
except debugmode.BadOptimization, e:
assert str(e.reasons[e.new_r][0][0]) == 'insert_broken_csc'
return #TEST PASS
assert False
...@@ -222,6 +222,10 @@ class Tensor(Type): ...@@ -222,6 +222,10 @@ class Tensor(Type):
"""Compare True iff other is the same kind of Tensor""" """Compare True iff other is the same kind of Tensor"""
return type(self) == type(other) and other.dtype == self.dtype and other.broadcastable == self.broadcastable return type(self) == type(other) and other.dtype == self.dtype and other.broadcastable == self.broadcastable
def values_eq_enough(self, a, b):
return type(a) is numpy.ndarray and type(b) is numpy.ndarray \
and (a.shape == b.shape) and numpy.allclose(a, b)
def __hash__(self): def __hash__(self):
"""Hash equal for same kinds of Tensor""" """Hash equal for same kinds of Tensor"""
return hash(self.dtype) ^ hash(self.broadcastable) return hash(self.dtype) ^ hash(self.broadcastable)
......
...@@ -13,7 +13,7 @@ from theano import pprint ...@@ -13,7 +13,7 @@ from theano import pprint
import numpy import numpy
#import scalar_opt #import scalar_opt
from theano.sandbox.debugmode import OptCheck from theano.sandbox.debugmode import DebugMode
from theano import function from theano import function
...@@ -136,7 +136,7 @@ class test_greedy_distribute(unittest.TestCase): ...@@ -136,7 +136,7 @@ class test_greedy_distribute(unittest.TestCase):
, eps + y/s , eps + y/s
, s) , s)
f = function([s, eps, x,y], r**2, mode=OptCheck()) f = function([s, eps, x,y], r**2, mode=DebugMode())
r0 = f(4,1.e-6, [1.5,2], [2.3,3.1]) r0 = f(4,1.e-6, [1.5,2], [2.3,3.1])
r1 = f(4,1.e-6, [1.5,2], [2.3,3.1]) r1 = f(4,1.e-6, [1.5,2], [2.3,3.1])
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论