提交 d676edaf authored 作者: Olivier Breuleux's avatar Olivier Breuleux

added base_tensor, some reorganizing, recovered some C support

上级 26d56c68
...@@ -5,6 +5,8 @@ import unittest ...@@ -5,6 +5,8 @@ import unittest
from copy import copy from copy import copy
from compile import Function from compile import Function
import gradient import gradient
import gof
#TODO: consider moving this function / functionality to gradient.py #TODO: consider moving this function / functionality to gradient.py
# rationale: it's tricky, and necessary everytime you want to verify # rationale: it's tricky, and necessary everytime you want to verify
...@@ -57,7 +59,7 @@ class T_tensor(unittest.TestCase): ...@@ -57,7 +59,7 @@ class T_tensor(unittest.TestCase):
def test0_int(self): # allocate from a scalar float def test0_int(self): # allocate from a scalar float
t = tensor(1) t = tensor(1)
self.failUnless(isinstance(t, Tensor)) self.failUnless(isinstance(t, Tensor))
self.failUnless(t.dtype == 'int64') self.failUnless(t.dtype == 'int64' or t.dtype == 'int32')
def test1(self): # allocate from a vector of ints, not broadcastable def test1(self): # allocate from a vector of ints, not broadcastable
t = tensor(numpy.ones(5,dtype='int32')) t = tensor(numpy.ones(5,dtype='int32'))
self.failUnless(isinstance(t, Tensor)) self.failUnless(isinstance(t, Tensor))
...@@ -141,6 +143,11 @@ def check_eq2(self, inputs, output, args_in, arg_out): ...@@ -141,6 +143,11 @@ def check_eq2(self, inputs, output, args_in, arg_out):
val = fn(*args_in) val = fn(*args_in)
self.failUnless( numpy.all(val == arg_out), (val, arg_out)) self.failUnless( numpy.all(val == arg_out), (val, arg_out))
def check_eq2(self, inputs, output, args_in, arg_out):
fn = Function(inputs, [output], linker_cls = gof.CLinker)
val = fn(*args_in)
self.failUnless( numpy.all(val == arg_out), (val, arg_out))
class T_abs(unittest.TestCase): class T_abs(unittest.TestCase):
def test_impl(self): def test_impl(self):
......
from gof import ResultBase
import numpy
from copy import copy
###########################
# BaseTensor Class
###########################
class BaseTensor(ResultBase):
"""ResultBase to store numpy.ndarray or equivalent via .data
Attributes:
_dtype - numpy dtype string such as 'int64' or 'float64' (among others)
_broadcastable - tuple of ints in (0,1) saying which dimensions of this
tensor are guaranteed to be 1, and up for broadcasting
Properties:
dtype - read-only access to _dtype, which should not be changed
broadcastable - read-only access to _broadcastable, which should not be changed
This class does not implement python operators and has no dependencies
on the Ops that use it.
"""
def __init__(self, dtype, broadcastable, role=None, name=None):
"""Initialize a Tensor"""
# data is not given here. This may seem a bit strange, but when data was
# an argument, it made sense to use *either* the given dtype,
# broadcastable, or override them from the fields of data. This makes
# the function ugly, especially because it isn't obvious how to set
# broadcastable from data.
#
# The only clean option I could think of, when passing a data arg was to
# require the broadcastable field to be given. Since broadcastable is
# the argument that is awkward to construct, I decided to put all this
# into the tensor(data,...) function below, which is like a second
# constructor that works with an ndarray.
ResultBase.__init__(self, role=role, name=name)
self._dtype = str(dtype)
self._broadcastable = tuple(broadcastable)
######################
# ResultBase interface
######################
#
# filter
#
def filter(self, arr):
if not isinstance(arr, numpy.ndarray):
arr = numpy.asarray(arr, dtype = self.dtype)
if len(self.broadcastable) != len(arr.shape):
raise ValueError(BaseTensor.filter.E_rank)
for b, s in zip(self.broadcastable, arr.shape):
if b and (s != 1):
raise ValueError(BaseTensor.filter.E_shape)
return arr
# these strings are here so that tests can use them
filter.E_rank = 'wrong rank'
filter.E_shape = 'non-unit size on broadcastable dimension'
#
# type information
#
def dtype_specs(self):
"""Return python - C type correspondance tuple for self.data
Return a tuple (python type, c type, numpy typenum) that corresponds to
self.dtype. It is for use in C code generation.
"""
#TODO: add more type correspondances for e.g. int32, int64, float32,
#complex64, etc.
return {'float64': (float, 'double', 'NPY_DOUBLE')}[self.dtype]
#
# C codegen stubs
#
def c_declare(self):
return """
PyArrayObject* %%(name)s;
int type_num_%%(name)s;
typedef %(dtype)s dtype_%%(name)s;
""" % dict(dtype = self.dtype_specs()[1])
def c_init(self):
return """
%%(name)s = NULL;
type_num_%%(name)s = %(type_num)s;
""" % dict(type_num = self.dtype_specs()[2])
def c_extract(self):
return """
%%(name)s = NULL;
type_num_%%(name)s = %(type_num)s;
if (py_%%(name)s == Py_None) {
// We can either fail here or set %%(name)s to NULL and rely on Ops using
// tensors to handle the NULL case, but if they fail to do so they'll end up
// with nasty segfaults, so this is public service.
PyErr_SetString(PyExc_ValueError, "expected an ndarray, not None");
%%(fail)s
//%%(name)s = NULL;
}
else if (!PyArray_Check(py_%%(name)s)) {
PyErr_SetString(PyExc_ValueError, "expected an ndarray");
%%(fail)s
}
else if (((PyArrayObject*)py_%%(name)s)->descr->type_num != %(type_num)s) {
PyErr_SetString(PyExc_ValueError, "expected %(type_num)s");
%%(fail)s
}
else {
%%(name)s = (PyArrayObject*)(py_%%(name)s);
Py_XINCREF(%%(name)s);
}
""" % dict(type_num = self.dtype_specs()[2])
def c_cleanup(self):
return """
if (%(name)s) {
Py_XDECREF(%(name)s);
}
"""
def c_sync(self):
return """
if (!%(name)s) {
Py_XDECREF(py_%(name)s);
py_%(name)s = Py_None;
}
else if ((void*)py_%(name)s != (void*)%(name)s) {
Py_XDECREF(py_%(name)s);
py_%(name)s = (PyObject*)%(name)s;
Py_XINCREF(py_%(name)s);
}
"""
def c_headers(self):
return []
def c_libraries(self):
return []
############################
# Tensor specific attributes
############################
dtype = property(lambda self: self._dtype)
broadcastable = property(lambda self: self._broadcastable)
############################
# Cloning facilities
############################
def __copy__(self):
return self.clone(True)
def clone(self, transfer_data = False):
"""
Returns a copy of this Tensor. If there is data stored inside it, it is also copied.
"""
cpy = self.__class__(self.dtype, self.broadcastable, None, self.name)
if transfer_data:
cpy.data = copy(self.data)
return cpy
from copy import copy from copy import copy
from gof import Op from gof import Op, Destroyer
from gof.utils import AbstractFunctionError from gof.utils import AbstractFunctionError
from tensor import _Op class Elemwise(Op):
class Elemwise(_Op):
def var_desc(self): def var_desc(self):
raise AbstractFunctionError() raise AbstractFunctionError()
...@@ -20,22 +18,6 @@ class Elemwise(_Op): ...@@ -20,22 +18,6 @@ class Elemwise(_Op):
return [[i[0] for i in idesc if i[1]], return [[i[0] for i in idesc if i[1]],
[o[0] for o in odesc if o[1]]] [o[0] for o in odesc if o[1]]]
def propagate_broadcastable(self, *inputs):
idesc, odesc = self.var_desc()
nonloop_o = [o[0] for o in odesc if not o[1]]
if nonloop_o:
raise Exception("Cannot infer broadcastable for non-loop variable(s) %s" % nonloop_o)
all_bcast = [broadcastable for broadcastable, i in zip(inputs, idesc) if i[1]]
if reduce(lambda x, y: x is not False and x == y and y, [len(x) for x in all_bcast]) is False:
raise TypeError("Inputs that are loop variables do not all have the same number of dimensions.")
ret = []
for arr in zip(*all_bcast):
if 0 in arr:
ret.append(0)
else:
ret.append(1)
return [ret] * self.nout
def c_code_init(self): def c_code_init(self):
raise AbstractFunctionError() raise AbstractFunctionError()
...@@ -76,7 +58,7 @@ class Elemwise(_Op): ...@@ -76,7 +58,7 @@ class Elemwise(_Op):
["%("+v+")s" for v in input_loop_vars], ["%("+v+")s" for v in input_loop_vars],
["%("+v+")s" for v in output_loop_vars], ["%("+v+")s" for v in output_loop_vars],
aliases) aliases)
return ret return ret
def c_validate_update(self): def c_validate_update(self):
......
...@@ -234,7 +234,11 @@ class CLinker(Linker): ...@@ -234,7 +234,11 @@ class CLinker(Linker):
env = self.env env = self.env
self.inputs = env.inputs self.inputs = env.inputs
if len(set(self.inputs)) != len(self.inputs):
raise Exception("CLinker doesn't support duplicate inputs.")
self.outputs = env.outputs self.outputs = env.outputs
if len(set(self.outputs)) != len(self.outputs):
raise Exception("CLinker doesn't support duplicate outputs.")
try: self.results = list(env.results()) try: self.results = list(env.results())
except AttributeError: self.results = self.inputs + self.outputs except AttributeError: self.results = self.inputs + self.outputs
......
import gof, gof.result import gof, gof.result
import numpy #for numeric_grad import numpy #for numeric_grad
from gof.python25 import all
_msg_retNone = 'op.grad(...) returned None, consider returning [None]' _msg_retNone = 'op.grad(...) returned None, consider returning [None]'
_msg_badlen = 'op.grad(...) returned wrong number of gradients' _msg_badlen = 'op.grad(...) returned wrong number of gradients'
......
...@@ -3,163 +3,22 @@ ...@@ -3,163 +3,22 @@
import numpy import numpy
from copy import copy from copy import copy
import inspect import inspect
from gof import ResultBase, Op, utils, Destroyer, Viewer from gof import ResultBase, Op, utils, Destroyer, Viewer, AbstractFunctionError
########################### from base_tensor import BaseTensor
# Tensor Class from elemwise import Elemwise
###########################
class Tensor(ResultBase):
"""ResultBase to store numpy.ndarray or equivalent via .data
Attributes:
_dtype - numpy dtype string such as 'int64' or 'float64' (among others)
_broadcastable - tuple of ints in (0,1) saying which dimensions of this
tensor are guaranteed to be 1, and up for broadcasting
Properties:
dtype - read-only access to _dtype, which should not be changed
broadcastable - read-only access to _broadcastable, which should not be changed
class Tensor(BaseTensor):
"""
This subclass of BaseTensor provides operator overloading using implementations
of Tensor operations contained in this file.
Operators: Operators:
- most numeric operators are overloaded to return Ops that *would* perform - most numeric operators are overloaded to return Ops that *would* perform
the corresponding calculation the corresponding calculation
""" """
def __init__(self, dtype, broadcastable, role=None, name=None):
"""Initialize a Tensor"""
# data is not given here. This may seem a bit strange, but when data was
# an argument, it made sense to use *either* the given dtype,
# broadcastable, or override them from the fields of data. This makes
# the function ugly, especially because it isn't obvious how to set
# broadcastable from data.
#
# The only clean option I could think of, when passing a data arg was to
# require the broadcastable field to be given. Since broadcastable is
# the argument that is awkward to construct, I decided to put all this
# into the tensor(data,...) function below, which is like a second
# constructor that works with an ndarray.
ResultBase.__init__(self, role=role, name=name)
self._dtype = str(dtype)
self._broadcastable = tuple(broadcastable)
######################
# ResultBase interface
######################
#
# filter
#
def filter(self, arr):
if not isinstance(arr, numpy.ndarray):
arr = numpy.asarray(arr, dtype = self.dtype)
if len(self.broadcastable) != len(arr.shape):
raise ValueError(Tensor.filter.E_rank)
for b, s in zip(self.broadcastable, arr.shape):
if b and (s != 1):
raise ValueError(Tensor.filter.E_shape)
return arr
# these strings are here so that tests can use them
filter.E_rank = 'wrong rank'
filter.E_shape = 'non-unit size on broadcastable dimension'
#
# type information : Olivier what does this mean?
#
def dtype_specs(self):
"""Return python - C type correspondance tuple for self.data
Return a tuple (python type, c type, numpy typenum) that corresponds to
self.dtype. It is for use in C code generation.
"""
#TODO: add more type correspondances for e.g. int32, int64, float32,
#complex64, etc.
return {'float64': (float, 'double', 'NPY_DOUBLE')}[self.dtype]
#
# C codegen stubs
#
def c_declare(self):
return """
PyArrayObject* %%(name)s;
int type_num_%%(name)s;
typedef %(dtype)s dtype_%%(name)s;
""" % dict(dtype = self.dtype_specs()[1])
def c_init(self):
return """
%%(name)s = NULL;
type_num_%%(name)s = %(type_num)s;
""" % dict(type_num = self.dtype_specs()[2])
def c_extract(self):
return """
%%(name)s = NULL;
type_num_%%(name)s = %(type_num)s;
if (py_%%(name)s == Py_None) {
%%(name)s = NULL;
}
else if (!PyArray_Check(py_%%(name)s)) {
PyErr_SetString(PyExc_ValueError, "expected an ndarray");
%%(fail)s
}
else if (((PyArrayObject*)py_%%(name)s)->descr->type_num != %(type_num)s) {
PyErr_SetString(PyExc_ValueError, "expected %(type_num)s");
%%(fail)s
}
else {
%%(name)s = (PyArrayObject*)(py_%%(name)s);
Py_XINCREF(%%(name)s);
}
""" % dict(type_num = self.dtype_specs()[2])
def c_cleanup(self):
return """
if (%(name)s) {
Py_XDECREF(%(name)s);
}
"""
def c_sync(self):
return """
if (!%(name)s) {
Py_XDECREF(py_%(name)s);
py_%(name)s = Py_None;
}
else if ((void*)py_%(name)s != (void*)%(name)s) {
Py_XDECREF(py_%(name)s);
py_%(name)s = (PyObject*)%(name)s;
Py_XINCREF(py_%(name)s);
}
"""
def c_headers(self):
return []
def c_libraries(self):
return []
############################
# Tensor specific attributes
#
############################
dtype = property(lambda self: self._dtype)
broadcastable = property(lambda self: self._broadcastable)
# STDLIB
def __copy__(self):
"""
Returns a copy of this Tensor. If there is data stored inside it, it is also copied.
"""
cpy = self.__class__(self.dtype, self.broadcastable, None, self.name)
cpy.data = copy(self.data)
return cpy
#UNARY #UNARY
def __abs__(self): return Abs(self).out def __abs__(self): return Abs(self).out
def __neg__(self): return Neg(self).out def __neg__(self): return Neg(self).out
...@@ -241,6 +100,20 @@ def _scalar_switch(normal_f, scalar_f, scalar_f_reverse = None): ...@@ -241,6 +100,20 @@ def _scalar_switch(normal_f, scalar_f, scalar_f_reverse = None):
return normal_f(x, y) return normal_f(x, y)
return f return f
def _assert_same_shapes(x, *rest):
"""Ensure that all inputs to the function impl have the same size (foils numpy's broadcasting)"""
shape = x.shape
for other in rest:
if other.shape != shape:
raise _assert_same_shapes.E_shape
_assert_same_shapes.E_shape = ValueError("The dimensions of the inputs do not match.")
def _assert_tensor_scalar(x, a):
"""ensure that the second input is a scalar"""
if numpy.product(a.shape) != 1:
raise ValueError("The second argument must be a scalar.")
class _Op(Op): class _Op(Op):
"""A convenient base for the ops in this file""" """A convenient base for the ops in this file"""
nin = -1 nin = -1
...@@ -288,7 +161,12 @@ class _Op(Op): ...@@ -288,7 +161,12 @@ class _Op(Op):
raise AbstractFunctionError() raise AbstractFunctionError()
def perform(self): def perform(self):
self.outputs[0].data = self.impl(*[input.data for input in self.inputs]) res = self.impl(*[input.data for input in self.inputs])
if self.nout == 1:
self.outputs[0].data = res
else:
for output, value in zip(self.outputs, res):
output.data = value
def c_var_names(self): def c_var_names(self):
(self, inames, onames), _1, _2, _3 = inspect.getargspec(self.c_impl) (self, inames, onames), _1, _2, _3 = inspect.getargspec(self.c_impl)
...@@ -308,20 +186,8 @@ class _Unary: ...@@ -308,20 +186,8 @@ class _Unary:
class _Binary: class _Binary:
nin = 2 nin = 2
def _assert_same_shapes(x, *rest):
"""Ensure that all inputs to the function impl have the same size (foils numpy's broadcasting)"""
shape = x.shape
for other in rest:
if other.shape != shape:
raise _assert_same_shapes.E_shape
_assert_same_shapes.E_shape = ValueError("The dimensions of the inputs do not match.")
def _assert_tensor_scalar(x, a): class _Elemwise(Elemwise, _Op):
"""ensure that the second input is a scalar"""
if numpy.product(a.shape) != 1:
raise ValueError("The second argument must be a scalar.")
class _Elemwise(_Op):
@staticmethod @staticmethod
def extract_name(name): def extract_name(name):
...@@ -333,28 +199,21 @@ class _Elemwise(_Op): ...@@ -333,28 +199,21 @@ class _Elemwise(_Op):
@staticmethod @staticmethod
def is_loop_var(name): def is_loop_var(name):
return name.endswith("_i") return name.endswith("_i")
def c_var_names(self):
cls = self.__class__
(self, inames, onames), _1, _2, _3 = inspect.getargspec(self.c_foreach)
spec = ([cls.extract_name(name) for name in inames],
[cls.extract_name(name) for name in onames])
return spec
def loop_variables(self): def var_desc(self):
cls = self.__class__ cls = self.__class__
(self, inames, onames), _1, _2, _3 = inspect.getargspec(cls.c_foreach) (self, inames, onames), _1, _2, _3 = inspect.getargspec(cls.c_foreach)
return ([cls.extract_name(name) for name in inames if cls.is_loop_var(name)], return ([(cls.extract_name(name), cls.is_loop_var(name)) for name in inames],
[cls.extract_name(name) for name in onames if cls.is_loop_var(name)]) [(cls.extract_name(name), cls.is_loop_var(name)) for name in onames])
def propagate_broadcastable(self, *inputs): def propagate_broadcastable(self, *inputs):
inames, onames = self.c_var_names() idesc, odesc = self.var_desc()
iloop, oloop = self.loop_variables() nonloop_o = [o[0] for o in odesc if not o[1]]
if oloop != onames: if nonloop_o:
raise Exception(\ raise Exception("Cannot infer broadcastable for non-loop variable(s) %s" % nonloop_o)
"Cannot infer broadcastable for non-loop variable(s) %s" \ all_bcast = [broadcastable for broadcastable, i in zip(inputs, idesc) if i[1]]
% set(onames).difference(oloop), self.__class__) if reduce(lambda x, y: x is not False and x == y and y, [len(x) for x in all_bcast]) is False:
all_bcast = [broadcastable for broadcastable, iname in zip(inputs, inames) if iname in iloop] raise TypeError("Inputs that are loop variables do not all have the same number of dimensions.")
ret = [] ret = []
for arr in zip(*all_bcast): for arr in zip(*all_bcast):
if 0 in arr: if 0 in arr:
...@@ -363,37 +222,38 @@ class _Elemwise(_Op): ...@@ -363,37 +222,38 @@ class _Elemwise(_Op):
ret.append(1) ret.append(1)
return [ret] * self.nout return [ret] * self.nout
@classmethod
def inplace_version(cls):
class Ret(cls, Destroyer):
def destroy_list(self):
return self.inputs[0]
return Ret
def c_init(self, inputs, outputs): def c_init(self, inputs, outputs):
pass raise AbstractFunctionError()
def c_foreach(self, inputs, outputs): def c_foreach(self, inputs, outputs):
pass raise AbstractFunctionError()
def c_finalize(self, inputs, outputs): def c_finalize(self, inputs, outputs):
pass raise AbstractFunctionError()
def c_code_init(self):
return self.c_init(self.inputs, self.outputs)
def c_code_foreach(self):
return self.c_foreach(self.inputs, self.outputs)
def c_code_finalize(self):
return self.c_finalize(self.inputs, self.outputs)
class TensorScalarOp(_Elemwise): class TensorScalarOp(_Elemwise):
def c_var_names(self): def var_desc(self):
return (['x', '_a'], ['z', ]) return [('x', 1), ('a', 0)], [('z', 1)]
def loop_variables(self): def c_code_init(self):
return (['x', ], ['z', ])
def c_init((x, _a), (z, )):
return """ return """
if (PyArray_SIZE(_a) != 1) { if (PyArray_SIZE(%(a)s) != 1) {
PyErr_SetString(PyExc_ValueError, \"The size of the scalar argument is not 1.\"); PyErr_SetString(PyExc_ValueError, \"The size of the scalar argument is not 1.\");
%(fail)s
} }
_a_dtype a = ((_a_dtype*)PyArray_DATA(_a))[0]; dtype_%(a)s _%(a)s = ((dtype_%(a)s*)PyArray_DATA(%(a)s))[0];
""" """
def _c_foreach(self): def c_code_foreach(self):
return "z_i = %s;" % self.c_expr return "%%(z)s_i = %s;" % self.c_expr
def constructor(op_cls): def constructor(op_cls):
def f(*args, **kwargs): def f(*args, **kwargs):
...@@ -404,6 +264,7 @@ def constructor(op_cls): ...@@ -404,6 +264,7 @@ def constructor(op_cls):
return op.outputs[0] return op.outputs[0]
return f return f
########################## ##########################
# Unary Operations # Unary Operations
########################## ##########################
...@@ -412,9 +273,9 @@ class Abs(_Elemwise): ...@@ -412,9 +273,9 @@ class Abs(_Elemwise):
def impl(self, x): def impl(self, x):
return numpy.abs(x) return numpy.abs(x)
def grad(self, x, gz): def grad(self, x, gz):
return gz * Sgn(x).out #TODO: handle the corner case (get it? pun?) return gz * Sgn(x).out #TODO: handle the corner case (get it? pun?) (there's a special place in hell for people like you)
def c_foreach(self, (x_i, ), (z_i, )): def c_foreach(self, (x_i, ), (z_i, )):
return "z_i = abs(x_i);" return "%(z)s_i = abs(%(x)s_i);"
#Constructor not necessary because builtin abs() does this #Constructor not necessary because builtin abs() does this
class Neg(_Elemwise): class Neg(_Elemwise):
...@@ -423,7 +284,7 @@ class Neg(_Elemwise): ...@@ -423,7 +284,7 @@ class Neg(_Elemwise):
def grad(self, x, gz): def grad(self, x, gz):
return -gz return -gz
def c_foreach(self, (x_i, ), (z_i, )): def c_foreach(self, (x_i, ), (z_i, )):
return "z_i = -x_i;" return "%(z)s_i = -%(x)s_i;"
#Constructor not necessary because unary '-' does this #Constructor not necessary because unary '-' does this
class Sgn(_Elemwise): class Sgn(_Elemwise):
...@@ -432,7 +293,7 @@ class Sgn(_Elemwise): ...@@ -432,7 +293,7 @@ class Sgn(_Elemwise):
def grad(self, x, gz): def grad(self, x, gz):
return [None] return [None]
def c_foreach(self, (x_i, ), (z_i, )): def c_foreach(self, (x_i, ), (z_i, )):
return "z_i = x_i/abs(x_i);" # TODO: C use copysign return "%(z)s_i = %(x)s_i/abs(%(x)s_i);" # TODO: C use copysign
sgn = constructor(Sgn) sgn = constructor(Sgn)
class Sum(_Elemwise): class Sum(_Elemwise):
...@@ -443,9 +304,9 @@ class Sum(_Elemwise): ...@@ -443,9 +304,9 @@ class Sum(_Elemwise):
def propagate_broadcastable(self, *inputs): def propagate_broadcastable(self, *inputs):
return [()] return [()]
def c_init(self, (x, ), (sum, )): def c_init(self, (x, ), (sum, )):
return "sum_dtype* sump = ((sum_dtype*)PyArray_DATA(sum)); sump[0] = 0;" return "dtype_%(sum)s* %(sum)sp = ((dtype_%(sum)s*)PyArray_DATA(%(sum)s)); %(sum)sp[0] = 0;"
def c_foreach(self, (x_i, ), (sum, )): def c_foreach(self, (x_i, ), (sum, )):
return "sump[0] += x_i;" return "%(sum)sp[0] += %(x)s_i;"
sum = constructor(Sum) sum = constructor(Sum)
class Fill(_Elemwise): class Fill(_Elemwise):
...@@ -454,9 +315,9 @@ class Fill(_Elemwise): ...@@ -454,9 +315,9 @@ class Fill(_Elemwise):
def grad(self, (model, value), gz): def grad(self, (model, value), gz):
return None, sum(gz) return None, sum(gz)
def c_init(self, (model, value), (z, )): def c_init(self, (model, value), (z, )):
return "value_dtype value0 = ((value_dtype*)PyArray_DATA(value))[0];" return "dtype_%(value)s %(value)s0 = ((dtype_%(value)s*)PyArray_DATA(%(value)s))[0];"
def c_foreach(self, (model_i, value), (z_i, )): def c_foreach(self, (model_i, value), (z_i, )):
return "z_i = value0;" return "%(z)s_i = %(value)s0;"
fill = constructor(Fill) fill = constructor(Fill)
...@@ -466,7 +327,7 @@ class TensorCopy(_Elemwise): ...@@ -466,7 +327,7 @@ class TensorCopy(_Elemwise):
def grad(self, x, gz): def grad(self, x, gz):
return gz return gz
def c_foreach(self, (x_i, ), (z_i, )): def c_foreach(self, (x_i, ), (z_i, )):
return "z_i = x_i;" return "%(z)s_i = %(x)s_i;"
tensor_copy = constructor(TensorCopy) tensor_copy = constructor(TensorCopy)
if 0: if 0:
...@@ -597,7 +458,7 @@ class MulElemwise(_Elemwise): ...@@ -597,7 +458,7 @@ class MulElemwise(_Elemwise):
def grad(self, (x, y), gz): def grad(self, (x, y), gz):
return mul(y, gz), mul(x, gz) return mul(y, gz), mul(x, gz)
def c_foreach(self, (x_i, y_i), (z_i, )): def c_foreach(self, (x_i, y_i), (z_i, )):
return "z_i = x_i * y_i;" return "%(z)s_i = %(x)s_i * %(y)s_i;"
mul_elemwise = constructor(MulElemwise) mul_elemwise = constructor(MulElemwise)
class MulElemwiseInplace(MulElemwise.inplace_version()): class MulElemwiseInplace(MulElemwise.inplace_version()):
...@@ -614,7 +475,7 @@ class Scale(TensorScalarOp): ...@@ -614,7 +475,7 @@ class Scale(TensorScalarOp):
return x * a return x * a
def grad(self, (x, a), gz): def grad(self, (x, a), gz):
return scale(a, gz), sum(mul_elemwise(x, gz)) return scale(a, gz), sum(mul_elemwise(x, gz))
c_expr = "x_i * a" c_expr = "%(x)s_i * _%(a)s"
scale = constructor(Scale) scale = constructor(Scale)
class ScaleInplace(Scale.inplace_version()): class ScaleInplace(Scale.inplace_version()):
......
...@@ -6,206 +6,6 @@ from tensor import * ...@@ -6,206 +6,6 @@ from tensor import *
# # TensorOp is a convenient base class, permitting to factor the code for the
# # Ops in this file.
# # It is not necessary to inherit from TensorOp to make an Op that manipulates
# # Tensors.
# <<<<<<< /u/breuleuo/hg/new_theano/tensor_ops.py
# class TensorOp(Op):
# nin = -1
# nout = 1
# cast_method = lambda self, *args: _upcast(*args)
# def __init__(self, *inputs):
# inputs = map(_wrap_as_tensor, inputs)
# if self.nin >= 0:
# if len(inputs) != self.nin:
# raise TypeError("Wrong number of inputs for %s (got %i, expected %i)") \
# % (self, len(inputs), self.nin)
# i_broadcastables = [getattr(input, 'broadcastable', None) for input in inputs]
# i_dtypes = [getattr(input, 'dtype', None) for input in inputs]
# o_broadcastables = utils.from_return_values(self.propagate_broadcastable(*i_broadcastables))
# o_dtypes = utils.from_return_values(self.propagate_dtype(*i_dtypes))
# self.inputs = inputs
# self.outputs = [Tensor(dtype, broadcastable) for broadcastable, dtype in zip(o_broadcastables, o_dtypes)]
# def propagate_broadcastable(self, *inputs):
# raise AbstractFunctionError()
# def propagate_dtype(self, *i_dtypes):
# for dtype in i_dtypes:
# if dtype is None:
# raise TypeError("Expected a Tensor.")
# return self.cast_method(*i_dtypes)
# def impl(self, *inputs):
# raise AbstractFunctionError()
# def perform(self):
# res = self.impl(*[input.data for input in self.inputs])
# if self.nout == 1:
# self.outputs[0].data = res
# else:
# for output, value in zip(self.outputs, res):
# output.data = value
# def c_var_names(self):
# (self, inames, onames), _1, _2, _3 = inspect.getargspec(self.c_impl)
# inames = utils.from_return_values(inames)
# onames = utils.from_return_values(onames)
# return [inames, onames]
# def c_code(self):
# return self.c_impl(self.inputs, self.outputs)
# def c_impl(self, inputs, outputs):
# raise AbstractFunctionError()
# class UnaryTensorOp(TensorOp):
# nin = 1
# class BinaryTensorOp(TensorOp):
# nin = 2
# # class Transpose(UnaryTensorOp):
# # def propagate_broadcastable(self, x):
# # x2 = copy(x)
# # x2.reverse()
# # return [x2]
# # def impl(self, x):
# # return x.T
# # def c_impl(self, x, z):
# # return """
# # PyArrayObject* transposed = (PyArrayObject*)PyArray_Transpose(%(x)s, NULL);
# # //if (PyArray_REFCOUNT(transposed) == 1) {
# # // printf("lala\\n");
# # //}
# # //if (%(z)s) {
# # // Py_XDECREF(%(z)s);
# # //}
# # %(z)s = transposed;
# # Py_XINCREF(%(z)s);
# # """
# def scalar_switch(normal_f, scalar_f, scalar_f_reverse = None):
# def f(x, y):
# x, y = _wrap_as_tensor(x), _wrap_as_tensor(y)
# if 0 not in y.broadcastable:
# return scalar_f(x, y)
# if 0 not in x.broadcastable:
# if scalar_f_reverse:
# return scalar_f_reverse(y, x)
# else:
# raise TypeError("You cannot do this operation on a scalar.")
# return normal_f(x, y)
# return f
# # Wrapper to ensure that all inputs to the function impl have the same size (foils numpy's broadcasting)
# def assert_same_shapes(x, *rest):
# shape = x.shape
# for other in rest:
# if other.shape != shape:
# raise ValueError("The dimensions of the inputs do not match.")
# # Wrapper to ensure that the last input to impl is a scalar
# def assert_tensor_scalar(x, a):
# if numpy.product(a.shape) != 1:
# raise ValueError("The second argument must be a scalar.")
# class Elemwise(TensorOp):
# @staticmethod
# def extract_name(name):
# if name.endswith("_i"):
# return name[:-2]
# else:
# return name
# @staticmethod
# def is_loop_var(name):
# return name.endswith("_i")
# def c_var_names(self):
# cls = self.__class__
# (self, inames, onames), _1, _2, _3 = inspect.getargspec(self.c_foreach)
# spec = ([cls.extract_name(name) for name in inames],
# [cls.extract_name(name) for name in onames])
# return spec
# def loop_variables(self):
# cls = self.__class__
# (self, inames, onames), _1, _2, _3 = inspect.getargspec(cls.c_foreach)
# return ([cls.extract_name(name) for name in inames if cls.is_loop_var(name)],
# [cls.extract_name(name) for name in onames if cls.is_loop_var(name)])
# def propagate_broadcastable(self, *inputs):
# inames, onames = self.c_var_names()
# iloop, oloop = self.loop_variables()
# if oloop != onames:
# raise Exception("Cannot infer broadcastable for non-loop variable(s) %s" % set(onames).difference(oloop))
# all_bcast = [broadcastable for broadcastable, iname in zip(inputs, inames) if iname in iloop]
# ret = []
# for arr in zip(*all_bcast):
# if 0 in arr:
# ret.append(0)
# else:
# ret.append(1)
# return [ret] * self.nout
# @classmethod
# def inplace_version(cls):
# class Ret(cls, Destroyer):
# def destroy_list(self):
# return self.inputs[0]
# return Ret
# def c_init(self, inputs, outputs):
# pass
# def c_foreach(self, inputs, outputs):
# pass
# def c_finalize(self, inputs, outputs):
# pass
# class TensorScalarOp(Elemwise):
# def c_var_names(self):
# return (['x', '_a'], ['z', ])
# def loop_variables(self):
# return (['x', ], ['z', ])
# def c_init((x, _a), (z, )):
# return """
# if (PyArray_SIZE(_a) != 1) {
# PyErr_SetString(PyExc_ValueError, \"The size of the scalar argument is not 1.\");
# }
# _a_dtype a = ((_a_dtype*)PyArray_DATA(_a))[0];
# """
# def _c_foreach(self):
# return "z_i = %s;" % self.c_expr
# =======
# >>>>>>> /tmp/tensor_ops.py~other.fNA50a
########################### ###########################
#### Binary Operations #### #### Binary Operations ####
########################### ###########################
...@@ -214,6 +14,11 @@ from tensor import * ...@@ -214,6 +14,11 @@ from tensor import *
## Dot ## ## Dot ##
######### #########
class Dot(TensorOp): class Dot(TensorOp):
@staticmethod @staticmethod
def _output_shape(xshape, yshape): def _output_shape(xshape, yshape):
...@@ -258,20 +63,12 @@ class Dot(TensorOp): ...@@ -258,20 +63,12 @@ class Dot(TensorOp):
class Neg(Elemwise):
def impl(self, x):
return -x
def grad(self, x, gz):
return -gz
def c_foreach(self, (x_i, ), (z_i, )):
return "z_i = -x_i;"
class NegInplace(Neg.inplace_version()): class NegInplace(Neg.inplace_version()):
def impl(self, x): def impl(self, x):
x *= -1 x *= -1
return x return x
class InvElemwise(Elemwise): class InvElemwise(Elemwise):
def impl(self, x): def impl(self, x):
return 1 / x return 1 / x
...@@ -425,3 +222,28 @@ class MinMax: ...@@ -425,3 +222,28 @@ class MinMax:
# # class Transpose(UnaryTensorOp):
# # def propagate_broadcastable(self, x):
# # x2 = copy(x)
# # x2.reverse()
# # return [x2]
# # def impl(self, x):
# # return x.T
# # def c_impl(self, x, z):
# # return """
# # PyArrayObject* transposed = (PyArrayObject*)PyArray_Transpose(%(x)s, NULL);
# # //if (PyArray_REFCOUNT(transposed) == 1) {
# # // printf("lala\\n");
# # //}
# # //if (%(z)s) {
# # // Py_XDECREF(%(z)s);
# # //}
# # %(z)s = transposed;
# # Py_XINCREF(%(z)s);
# # """
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论