提交 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'
......
差异被折叠。
...@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论