提交 fdfbab37 authored 作者: abergeron's avatar abergeron

Merge pull request #3747 from Sentient07/issue-3094

Quick python op with gradient
......@@ -64,6 +64,13 @@ possibilities you may encounter or need. For that refer to
# Properties attribute
__props__ = ()
#itypes and otypes attributes are
#compulsory if make_node method is not defined.
#They're the type of input and output respectively
itypes = None
otypes = None
#Compulsory if itypes and otypes are not defined
def make_node(self, *inputs):
pass
......@@ -98,7 +105,7 @@ possibilities you may encounter or need. For that refer to
.. ../extending/op.txt
An op has to implement some methods defined in the the interface of
:class:`gof.Op`. More specifically, it is mandatory for an op to define the method :func:`make_node` and one of the implementation methods, either :func:`perform`, :meth:`Op.c_code` or :func:`make_thunk`.
:class:`gof.Op`. More specifically, it is mandatory for an op to define either the method :func:`make_node` or :attr:`itypes`, :attr:`otypes` and one of the implementation methods, either :func:`perform`, :meth:`Op.c_code` or :func:`make_thunk`.
:func:`make_node` method creates an Apply node representing the application
of the op on the inputs provided. This method is reponsible for three things:
......@@ -175,6 +182,10 @@ An op has to implement some methods defined in the the interface of
to obtain the op's implementation.
:func:`perform` and :meth:`Op.c_code` will be ignored.
If :func:`make_node` is not defined, the :attr:`itypes` and :attr:`otypes`
are used by the Op's :func:`make_node` method to implement the functionality
of :func:`make_node` method mentioned above.
Other methods can be optionally defined by the op.
The :func:`__str__` method provides a meaningful string representation of
......@@ -254,7 +265,10 @@ Op Example
import theano
class DoubleOp(theano.Op):
#Using make_node
class DoubleOp1(theano.Op):
__props__ = ()
def make_node(self, x):
......@@ -286,12 +300,41 @@ Op Example
return eval_points
return self.grad(inputs, eval_points)
#Using itypes and otypes
class DoubleOp2(theano.Op):
__props__ = ()
itypes = [theano.tensor.dmatrix]
otypes = [theano.tensor.dmatrix]
def perform(self, node, inputs, output_storage):
x = inputs[0]
z = output_storage[0]
z[0] = x * 2
def infer_shape(self, node, i0_shapes):
return i0_shapes
def grad(self, inputs, output_grads):
return [output_grads[0] * 2]
def R_op(self, inputs, eval_points):
# R_op can receive None as eval_points.
# That mean there is no diferientiable path through that input
# If this imply that you cannot compute some outputs,
# return None for those.
if eval_points[0] is None:
return eval_points
return self.grad(inputs, eval_points)
You can try it as follows:
.. testcode:: example
.. testcode:: example(Using make_node)
x = theano.tensor.matrix()
f = theano.function([x], DoubleOp()(x))
f = theano.function([x], DoubleOp1()(x))
import numpy
inp = numpy.random.rand(5, 4)
out = f(inp)
......@@ -299,6 +342,38 @@ You can try it as follows:
print(inp)
print(out)
.. testoutput:: example
:hide:
:options: +ELLIPSIS
...
...
.. code-block:: none
[[ 0.08257206 0.34308357 0.5288043 0.06582951]
[ 0.65977826 0.10040307 0.5402353 0.55472296]
[ 0.82358552 0.29502171 0.97387481 0.0080757 ]
[ 0.77327215 0.65401857 0.76562992 0.94145702]
[ 0.8452076 0.30500101 0.88430501 0.95818655]]
[[ 0.16514411 0.68616713 1.0576086 0.13165902]
[ 1.31955651 0.20080613 1.08047061 1.10944593]
[ 1.64717104 0.59004341 1.94774962 0.0161514 ]
[ 1.5465443 1.30803715 1.53125983 1.88291403]
[ 1.6904152 0.61000201 1.76861002 1.9163731 ]]
.. testcode:: example (Using itypes and otypes)
x = theano.tensor.matrix()
f = theano.function([x], DoubleOp2()(x))
import numpy
inp = numpy.random.rand(5, 4)
out = f(inp)
assert numpy.allclose(inp * 2, out)
print(inp)
print(out)
.. testoutput:: example
:hide:
:options: +ELLIPSIS
......
......@@ -518,16 +518,6 @@ class FromFunctionOp(gof.Op):
def __str__(self):
return 'FromFunctionOp{%s}' % self.__fn.__name__
def make_node(self, *inputs):
if len(inputs) != len(self.itypes):
raise ValueError("We expected %d inputs but got %d." %
(len(self.itypes), len(inputs)))
if not all(inp.type == it for inp, it in zip(inputs, self.itypes)):
raise TypeError(
"We expected inputs of types '%s' but got types '%s' " %
(str([inp.type for inp in inputs]), str(self.itypes)))
return theano.Apply(self, inputs, [o() for o in self.otypes])
def perform(self, node, inputs, outputs):
outs = self.__fn(*inputs)
if not isinstance(outs, (list, tuple)):
......
......@@ -16,7 +16,7 @@ import pickle
# reachable from pickle (as in it cannot be defined inline)
@as_op([dmatrix, dmatrix], dmatrix)
def mul(a, b):
return a*b
return a * b
class OpDecoratorTests(utt.InferShapeTester):
......
......@@ -38,6 +38,7 @@ class CLinkerObject(object):
Standard elements of an Op or Type used with the CLinker.
"""
def c_headers(self):
"""
Optional: Return a list of header files required by code returned by
......@@ -59,7 +60,8 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_headers", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_headers", type(self), self.__class__.__name__)
def c_header_dirs(self):
"""
......@@ -82,7 +84,10 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_header_dirs", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_header_dirs",
type(self),
self.__class__.__name__)
def c_libraries(self):
"""
......@@ -105,7 +110,8 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_libraries", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_libraries", type(self), self.__class__.__name__)
def c_lib_dirs(self):
"""
......@@ -128,7 +134,8 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_lib_dirs", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_lib_dirs", type(self), self.__class__.__name__)
def c_support_code(self):
"""
......@@ -144,7 +151,10 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_support_code", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_support_code",
type(self),
self.__class__.__name__)
def c_code_cache_version(self):
"""
......@@ -182,7 +192,10 @@ class CLinkerObject(object):
Subclass does not implement this method.
"""
raise utils.MethodNotDefined("c_compile_args", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_compile_args",
type(self),
self.__class__.__name__)
def c_no_compile_args(self):
"""
......@@ -203,7 +216,10 @@ class CLinkerObject(object):
The subclass does not override this method.
"""
raise utils.MethodNotDefined("c_no_compile_args", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"c_no_compile_args",
type(self),
self.__class__.__name__)
def c_init_code(self):
"""
......@@ -505,14 +521,9 @@ class PureOp(object):
Required: return an Apply instance representing the
application of this Op to the provided inputs.
All subclasses should over-ride this function.
Raises
------
MethodNotDefined : the subclass does not override this method.
"""
raise utils.MethodNotDefined("make_node", type(self), self.__class__.__name__)
raise utils.MethodNotDefined(
"make_node", type(self), self.__class__.__name__)
@classmethod
def _get_test_value(cls, v):
......@@ -542,7 +553,7 @@ class PureOp(object):
"For compute_test_value, one input test value does not"
" have the requested type.\n")
tr = getattr(v.tag, 'trace', [])
if type(tr) is list and len(tr) > 0:
if isinstance(tr, list) and len(tr) > 0:
detailed_err_msg += (
" \nBacktrace when that variable is created:\n")
# Print separate message for each element in the list
......@@ -612,10 +623,14 @@ class PureOp(object):
except AttributeError:
# no test-value was specified, act accordingly
if config.compute_test_value == 'warn':
warnings.warn('Warning, Cannot compute test value: input %i (%s) of Op %s missing default value' % (i, ins, node), stacklevel=2)
warnings.warn(
'Warning, Cannot compute test value: input %i (%s) of Op %s missing default value' %
(i, ins, node), stacklevel=2)
run_perform = False
elif config.compute_test_value == 'raise':
raise ValueError('Cannot compute test value: input %i (%s) of Op %s missing default value' % (i, ins, node))
raise ValueError(
'Cannot compute test value: input %i (%s) of Op %s missing default value' %
(i, ins, node))
elif config.compute_test_value == 'ignore':
# silently skip test
run_perform = False
......@@ -623,7 +638,9 @@ class PureOp(object):
import pdb
pdb.post_mortem(sys.exc_info()[2])
else:
raise ValueError('%s is invalid for option config.compute_Test_value' % config.compute_test_value)
raise ValueError(
'%s is invalid for option config.compute_Test_value' %
config.compute_test_value)
# if all inputs have test-values, run the actual op
if run_perform:
......@@ -653,7 +670,8 @@ class PureOp(object):
for output in node.outputs:
# Check that the output has been computed
assert compute_map[output][0], (output, storage_map[output][0])
assert compute_map[output][
0], (output, storage_map[output][0])
# add 'test_value' to output tag, so that downstream ops can use these
# numerical values as inputs to their perform method.
......@@ -813,7 +831,8 @@ class Op(utils.object2, PureOp, CLinkerOp):
def __eq__(self, other):
if hasattr(self, '__props__'):
return (type(self) == type(other) and self._props() == other._props())
return (type(self) == type(other) and self._props() ==
other._props())
else:
return NotImplemented
......@@ -950,6 +969,25 @@ class Op(utils.object2, PureOp, CLinkerOp):
# condition: either there was no c_code, or it failed
return self.make_py_thunk(node, storage_map, compute_map, no_recycling)
def make_node(self, *inputs):
if not hasattr(self, 'itypes'):
raise NotImplementedError("You can either define itypes and otypes,\
or implement make_node")
if not hasattr(self, 'otypes'):
raise NotImplementedError("You can either define itypes and otypes,\
or implement make_node")
if len(inputs) != len(self.itypes):
raise ValueError("We expected %d inputs but got %d." %
(len(self.itypes), len(inputs)))
if not all(inp.type == it for inp, it in zip(inputs, self.itypes)):
raise TypeError(
"We expected inputs of types '%s' but got types '%s' " %
(str([inp.type for inp in inputs]), str(self.itypes)))
return theano.Apply(self, inputs, [o() for o in self.otypes])
def get_test_value(v):
"""
......@@ -1218,7 +1256,9 @@ class COp(Op):
"""
section_re = re.compile(r'^#section ([a-zA-Z0-9_]+)$', re.MULTILINE)
backward_re = re.compile(r'^THEANO_(APPLY|SUPPORT)_CODE_SECTION$', re.MULTILINE)
backward_re = re.compile(
r'^THEANO_(APPLY|SUPPORT)_CODE_SECTION$',
re.MULTILINE)
# This is the set of allowed markers
SECTIONS = set([
'init_code', 'init_code_apply', 'init_code_struct',
......@@ -1318,8 +1358,9 @@ class COp(Op):
n = 1
while n < len(split):
if split[n] not in self.SECTIONS:
raise ValueError("Unknown section type (in file %s): %s" %
(self.func_files[i], split[n]))
raise ValueError(
"Unknown section type (in file %s): %s" %
(self.func_files[i], split[n]))
if split[n] not in self.code_sections:
self.code_sections[split[n]] = ""
self.code_sections[split[n]] += split[n + 1]
......@@ -1387,7 +1428,9 @@ class COp(Op):
macro_name = "DTYPE_" + vname
macro_value = "npy_" + v.dtype
define_macros.append(define_template % (macro_name, macro_value))
define_macros.append(
define_template %
(macro_name, macro_value))
undef_macros.append(undef_template % macro_name)
d = numpy.dtype(v.dtype)
......@@ -1395,13 +1438,17 @@ class COp(Op):
macro_name = "TYPENUM_" + vname
macro_value = d.num
define_macros.append(define_template % (macro_name, macro_value))
define_macros.append(
define_template %
(macro_name, macro_value))
undef_macros.append(undef_template % macro_name)
macro_name = "ITEMSIZE_" + vname
macro_value = d.itemsize
define_macros.append(define_template % (macro_name, macro_value))
define_macros.append(
define_template %
(macro_name, macro_value))
undef_macros.append(undef_template % macro_name)
# Generate a macro to mark code as being apply-specific
......
......@@ -156,6 +156,7 @@ class TestOp:
class TestMakeThunk(unittest.TestCase):
def test_no_c_code(self):
class IncOnePython(Op):
"""An Op with only a Python (perform) implementation"""
......@@ -233,6 +234,26 @@ class TestMakeThunk(unittest.TestCase):
self.assertRaises((NotImplementedError, utils.MethodNotDefined),
thunk)
def test_no_make_node(self):
class DoubleOp(Op):
"""An Op without make_node"""
__props__ = ()
itypes = [T.dmatrix]
otypes = [T.dmatrix]
def perform(self, node, inputs, outputs):
inp = inputs[0]
output = outputs[0]
output[0] = inp * 2
x_input = T.dmatrix('x_input')
f = theano.function([x_input], DoubleOp()(x_input))
inp = numpy.random.rand(5, 4)
out = f(inp)
assert numpy.allclose(inp * 2, out)
def test_test_value_python_objects():
for x in ([0, 1, 2], 0, 0.5, 1):
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论