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

merge

...@@ -7,6 +7,8 @@ import gradient ...@@ -7,6 +7,8 @@ import gradient
from sparse import _is_dense, _is_sparse, _is_dense_result, _is_sparse_result from sparse import _is_dense, _is_sparse, _is_dense_result, _is_sparse_result
from sparse import _mtypes, _mtype_to_str from sparse import _mtypes, _mtype_to_str
import random
class T_transpose(unittest.TestCase): class T_transpose(unittest.TestCase):
def setUp(self): def setUp(self):
numpy.random.seed(44) numpy.random.seed(44)
......
...@@ -572,6 +572,17 @@ def check_eq2_both(self, inputs, output, args_in, arg_out): ...@@ -572,6 +572,17 @@ def check_eq2_both(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))
class T_Shape(unittest.TestCase):
def test_basic0(self):
s = shape(numpy.ones((5, 3)))
self.failUnless((eval_outputs([s]) == [5, 3]).all())
def test_basic1(self):
s = shape(numpy.ones((2)))
self.failUnless((eval_outputs([s]) == [2]).all())
def test_basic2(self):
s = shape(numpy.ones((5, 3, 10)))
self.failUnless((eval_outputs([s]) == [5, 3, 10]).all())
class T_argmax(unittest.TestCase): class T_argmax(unittest.TestCase):
def setUp(self): def setUp(self):
numpy.random.seed(123784) numpy.random.seed(123784)
...@@ -680,149 +691,197 @@ class T_transpose(unittest.TestCase): ...@@ -680,149 +691,197 @@ class T_transpose(unittest.TestCase):
verify_grad(self, transpose_inplace, [numpy.random.rand(2, 3)]) verify_grad(self, transpose_inplace, [numpy.random.rand(2, 3)])
verify_grad(self, transpose_inplace, [numpy.ones(3)]) verify_grad(self, transpose_inplace, [numpy.ones(3)])
# class T_subtensor(unittest.TestCase): class T_subtensor(unittest.TestCase):
# def test0_err_invalid(self): def setUp(self):
# #it is impossible to retrieve a view of a 0-d tensor Subtensor.debug = False
# n = astensor(numpy.ones(())) numpy.random.seed(12353123)
# try:
# t = n[0] def test0_err_invalid(self):
# except ValueError, e: #it is impossible to retrieve a view of a 0-d tensor
# self.failUnless(e[0] is Subtensor.e_invalid) n = astensor(numpy.ones(()))
# return try:
# self.fail() t = n[0]
# def test1_err_bounds(self): except ValueError, e:
# n = astensor(numpy.ones(3)) self.failUnless(e[0] is Subtensor.e_invalid)
# t = n[7] return
# self.failUnless(t.owner.__class__ is Subtensor) self.fail()
# try: def test1_err_bounds(self):
# tval = eval_outputs([t]) n = astensor(numpy.ones(3))
# except Exception, e: t = n[7]
# if e[0] != 'index out of bounds': self.failUnless(t.owner.__class__ is Subtensor)
# raise try:
# return tval = eval_outputs([t])
# self.fail() except Exception, e:
# def test1_ok_range_finite(self): if e[0] != 'index out of bounds':
# n = astensor(numpy.ones(3)*5) raise
# t = n[0:2] return
# self.failUnless(t.owner.__class__ is Subtensor) self.fail()
# tval = eval_outputs([t]) def test1_ok_range_finite(self):
# self.failUnless(tval.shape == (2,)) n = astensor(numpy.ones(3)*5)
# self.failUnless(tval[1] == 5.0) t = n[0:2]
# def test2_ok_range_finite(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.ones((3,4))*5) tval = eval_outputs([t])
# t = n[0:2,3] self.failUnless(tval.shape == (2,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(tval[1] == 5.0)
# tval = eval_outputs([t]) def test2_ok_range_finite(self):
# self.failUnless(tval.shape == (2,)) n = astensor(numpy.ones((3,4))*5)
# self.failUnless(tval[1] == 5.0) t = n[0:2,3]
# def test1_err_invalid(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.ones(1)) tval = eval_outputs([t])
# try: self.failUnless(tval.shape == (2,))
# t = n[0,0] self.failUnless(tval[1] == 5.0)
# except ValueError, e: def test1_err_invalid(self):
# self.failUnless(e[0] is Subtensor.e_invalid) n = astensor(numpy.ones(1))
# return try:
# self.fail() t = n[0,0]
# def test1_ok_elem(self): except ValueError, e:
# n = astensor(numpy.ones(1)*5) self.failUnless(e[0] is Subtensor.e_invalid)
# t = n[0] return
# self.failUnless(t.owner.__class__ is Subtensor) self.fail()
# tval = eval_outputs([t]) def test1_ok_elem(self):
# self.failUnless(tval.shape == ()) n = astensor(numpy.ones(1)*5)
# self.failUnless(tval == 5.0) t = n[0]
# def test1_ok_range_infinite(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.ones(3)*5) tval = eval_outputs([t])
# t = n[1:] self.failUnless(tval.shape == ())
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(tval == 5.0)
# tval = eval_outputs([t]) def test1_ok_range_infinite(self):
# self.failUnless(tval.shape == (2,)) #Subtensor.debug = True
# self.failUnless(tval[1] == 5.0) n = astensor(numpy.ones(3)*5)
# def test1_ok_strided(self): t = n[1:]
# n = astensor(numpy.ones(5)*5) self.failUnless(t.owner.__class__ is Subtensor)
# t = n[1::2] tval = eval_outputs([t])
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(tval.shape == (2,))
# tval = eval_outputs([t]) self.failUnless(tval[1] == 5.0)
# self.failUnless(tval.shape == (2,)) def test1_ok_strided(self):
# self.failUnless(tval[1] == 5.0) n = astensor(numpy.ones(5)*5)
t = n[1::2]
# tval = eval_outputs([n[0:-1:2]]) #0 to 1 from the end stepping by 2 self.failUnless(t.owner.__class__ is Subtensor)
# self.failUnless(tval.shape == (2,)) tval = eval_outputs([t])
# self.failUnless(tval[1] == 5.0) self.failUnless(tval.shape == (2,))
self.failUnless(tval[1] == 5.0)
# def test2_err_bounds0(self):
# n = astensor(numpy.ones((2,3))*5) tval = eval_outputs([n[0:-1:2]]) #0 to 1 from the end stepping by 2
# t = n[0,4] self.failUnless(tval.shape == (2,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(tval[1] == 5.0)
# try:
# tval = eval_outputs([t]) def test2_err_bounds0(self):
# except IndexError, e: n = astensor(numpy.ones((2,3))*5)
# return t = n[0,4]
# self.fail() self.failUnless(t.owner.__class__ is Subtensor)
# def test2_err_bounds1(self): try:
# n = astensor(numpy.ones((2,3))*5) tval = eval_outputs([t])
# t = n[4:5,2] except IndexError, e:
# self.failUnless(t.owner.__class__ is Subtensor) return
# try: self.fail()
# tval = eval_outputs([t]) def test2_err_bounds1(self):
# except Exception, e: n = astensor(numpy.ones((2,3))*5)
# if e[0] != 'index out of bounds': t = n[4:5,2]
# raise self.failUnless(t.owner.__class__ is Subtensor)
# def test2_ok_elem(self): try:
# n = astensor(numpy.asarray(range(6)).reshape((2,3))) tval = eval_outputs([t])
# t = n[0,2] except Exception, e:
# self.failUnless(t.owner.__class__ is Subtensor) if e[0] != 'index out of bounds':
# tval = eval_outputs([t]) raise
# self.failUnless(tval.shape == ()) def test2_ok_elem(self):
# self.failUnless(numpy.all(tval == 2)) n = astensor(numpy.asarray(range(6)).reshape((2,3)))
# def test2_ok_row(self): t = n[0,2]
# n = astensor(numpy.asarray(range(6)).reshape((2,3))) self.failUnless(t.owner.__class__ is Subtensor)
# t = n[1] tval = eval_outputs([t])
# self.failIf(any(n.broadcastable)) self.failUnless(tval.shape == ())
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == 2))
# tval = eval_outputs([t]) def test2_ok_row(self):
# self.failUnless(tval.shape == (3,)) n = astensor(numpy.asarray(range(6)).reshape((2,3)))
# self.failUnless(numpy.all(tval == [3,4,5])) t = n[1]
self.failIf(any(n.broadcastable))
# def test2_ok_col(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.ones((2,3))*5) tval = eval_outputs([t])
# t = n[:,0] self.failUnless(tval.shape == (3,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == [3,4,5]))
# self.failIf(any(n.broadcastable))
# tval = eval_outputs([t]) def test2_ok_col(self):
# self.failUnless(tval.shape == (2,)) n = astensor(numpy.ones((2,3))*5)
# self.failUnless(numpy.all(tval == 5.0)) t = n[:,0]
self.failUnless(t.owner.__class__ is Subtensor)
# def test2_ok_rows_finite(self): self.failIf(any(n.broadcastable))
# n = astensor(numpy.ones((4,3))*5) tval = eval_outputs([t])
# t = n[1:3,0] self.failUnless(tval.shape == (2,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == 5.0))
# tval = eval_outputs([t])
# self.failUnless(tval.shape == (2,)) def test2_ok_rows_finite(self):
# self.failUnless(numpy.all(tval == 5.0)) n = astensor(numpy.ones((4,3))*5)
t = n[1:3,0]
# def test2_ok_cols_infinite(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.asarray(range(12)).reshape((4,3))) tval = eval_outputs([t])
# t = n[1,2:] self.failUnless(tval.shape == (2,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == 5.0))
# tval = eval_outputs([t])
# self.failUnless(tval.shape == (1,)) def test2_ok_cols_infinite(self):
# self.failUnless(numpy.all(tval == 5)) n = astensor(numpy.asarray(range(12)).reshape((4,3)))
t = n[1,2:]
# def test2_ok_strided(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.asarray(range(20)).reshape((4,5))) tval = eval_outputs([t])
# t = n[1:4:2,1:5:2] self.failUnless(tval.shape == (1,))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == 5))
# tval = eval_outputs([t])
# self.failUnless(tval.shape == (2,2)) def test2_ok_strided(self):
# self.failUnless(numpy.all(tval == [[6, 8],[16, 18]])) n = astensor(numpy.asarray(range(20)).reshape((4,5)))
t = n[1:4:2,1:5:2]
# def test3_ok_mat(self): self.failUnless(t.owner.__class__ is Subtensor)
# n = astensor(numpy.asarray(range(24)).reshape((2,3,4))) tval = eval_outputs([t])
# t = n[0,0,0] self.failUnless(tval.shape == (2,2))
# self.failUnless(t.owner.__class__ is Subtensor) self.failUnless(numpy.all(tval == [[6, 8],[16, 18]]))
# tval = eval_outputs([t])
# self.failUnless(tval.shape == ()) def test3_ok_mat(self):
# self.failUnless(numpy.all(tval == 0)) n = astensor(numpy.asarray(range(24)).reshape((2,3,4)))
t = n[0,0,0]
self.failUnless(t.owner.__class__ is Subtensor)
tval = eval_outputs([t])
self.failUnless(tval.shape == ())
self.failUnless(numpy.all(tval == 0))
def test_grad_1d(self):
n = astensor(numpy.random.rand(2,3))
z = scal.constant(0)
t = n[z:,z]
gn = gradient.grad(sum(exp(t)), n)
gval = eval_outputs([gn])
s0 = 'array([ 2.05362099, 0. , 0. ])'
s1 = 'array([ 1.55009327, 0. , 0. ])'
self.failUnless(repr(gval[0,:]) == s0)
self.failUnless(repr(gval[1,:]) == s1)
def test_grad_0d(self):
n = astensor(numpy.random.rand(2,3))
t = n[1,0]
gn = gradient.grad(sum(exp(t)), n)
gval = eval_outputs([gn])
g0 = repr(gval[0,:])
g1 = repr(gval[1,:])
s0 = 'array([ 0., 0., 0.])'
s1 = 'array([ 1.55009327, 0. , 0. ])'
self.failUnless(g0 == s0, (g0, s0))
self.failUnless(g1 == s1, (g1, s1))
class T_Stack(unittest.TestCase):
def test_hstack(self):
a = astensor(numpy.array([[1, 2, 3], [4, 5, 6]]), broadcastable=[False,False])
b = astensor(numpy.array([[7], [8]]), broadcastable=[False,False])
s = horizontal_stack(a, b)
c = numpy.array([[1, 2, 3, 7], [4, 5, 6, 8]])
self.failUnless((eval_outputs([s]) == c).all())
def test_vstack(self):
a = astensor(numpy.array([[1, 2, 3], [4, 5, 6]]), broadcastable=[False,False])
b = astensor(numpy.array([[7, 8, 9]]), broadcastable=[False,False])
s = vertical_stack(a, b)
c = numpy.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
self.failUnless((eval_outputs([s]) == c).all())
# class T_add(unittest.TestCase): # class T_add(unittest.TestCase):
...@@ -964,7 +1023,6 @@ class T_transpose(unittest.TestCase): ...@@ -964,7 +1023,6 @@ class T_transpose(unittest.TestCase):
# self.fail() # self.fail()
# except ValueError, e: # except ValueError, e:
# self.failUnless('shape mismatch' in str(e)) # self.failUnless('shape mismatch' in str(e))
# try: # try:
# check_eq2_c(self, [a,b], Mul(a,b).out, # check_eq2_c(self, [a,b], Mul(a,b).out,
# [numpy.ones(3), numpy.ones(4)], 1.0) # [numpy.ones(3), numpy.ones(4)], 1.0)
...@@ -1284,7 +1342,34 @@ class t_gemm(unittest.TestCase): ...@@ -1284,7 +1342,34 @@ class t_gemm(unittest.TestCase):
return return
self.fail() self.fail()
class T_tensorfromscalar(unittest.TestCase):
def test0(self):
s = scal.constant(56)
t = tensor_from_scalar(s)
self.failUnless(t.owner.__class__ is TensorFromScalar)
self.failUnless(t.broadcastable == (), t.broadcastable)
self.failUnless(t.ndim == 0, t.ndim)
self.failUnless(t.dtype == s.dtype)
v = eval_outputs([t])
self.failUnless(v == 56, v)
self.failUnless(isinstance(v, numpy.ndarray))
self.failUnless(v.shape == (), v.shape)
def test1(self):
s = scal.constant(56)
t = astensor(s)
self.failUnless(t.owner.__class__ is TensorFromScalar)
self.failUnless(t.broadcastable == (), t.broadcastable)
self.failUnless(t.ndim == 0, t.ndim)
self.failUnless(t.dtype == s.dtype)
v = eval_outputs([t])
self.failUnless(v == 56, v)
self.failUnless(isinstance(v, numpy.ndarray))
self.failUnless(v.shape == (), v.shape)
# def _tensor(data, broadcastable=None, name=None): # def _tensor(data, broadcastable=None, name=None):
...@@ -1424,4 +1509,8 @@ class t_gemm(unittest.TestCase): ...@@ -1424,4 +1509,8 @@ class t_gemm(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
<<<<<<< /u/breuleuo/hg/theano2/_test_tensor.py
#AddTester('test_grad').debug() #AddTester('test_grad').debug()
=======
>>>>>>> /tmp/_test_tensor.py~other.dM43H3
...@@ -25,37 +25,37 @@ class _test_inplace_opt(unittest.TestCase): ...@@ -25,37 +25,37 @@ class _test_inplace_opt(unittest.TestCase):
x, y, z = inputs() x, y, z = inputs()
e = x + y + z e = x + y + z
g = Env([x, y], [e]) g = Env([x, y], [e])
assert str(g) == "[Broadcast{Add}(Broadcast{Add}(x, y), z)]" self.failUnless(str(g) == "[Broadcast{Add}(Broadcast{Add}(x, y), z)]")
inplace_optimizer.optimize(g) inplace_optimizer.optimize(g)
assert str(g) == "[Broadcast{Add}{0: 0}(Broadcast{Add}{0: 0}(x, y), z)]" self.failUnless(str(g) == "[Broadcast{Add}{0: 0}(Broadcast{Add}{0: 0}(x, y), z)]")
def test_multiple_uses(self): def test_multiple_uses(self):
x, y, z = inputs() x, y, z = inputs()
e0 = x + y e0 = x + y
e1 = x * y e1 = x * y
g = Env([x, y], [e0, e1]) g = Env([x, y], [e0, e1])
assert str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}(x, y)]" self.failUnless(str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}(x, y)]")
inplace_optimizer.optimize(g) inplace_optimizer.optimize(g)
assert str(g) == "[Broadcast{Add}{0: 0}(x, y), Broadcast{Mul}(x, y)]" \ self.failUnless(str(g) == "[Broadcast{Add}{0: 0}(x, y), Broadcast{Mul}(x, y)]" \
or str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]" or str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]")
def test_user_inplace(self): def test_user_inplace(self):
x, y, z = inputs() x, y, z = inputs()
e0 = x + y e0 = x + y
e1 = tensor.mul_inplace(x, y) e1 = tensor.mul_inplace(x, y)
g = Env([x, y], [e0, e1]) g = Env([x, y], [e0, e1])
assert str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]" self.failUnless(str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]")
inplace_optimizer.optimize(g) inplace_optimizer.optimize(g)
assert str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]" self.failUnless(str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, y)]")
def test_inplace_on_second_argument(self): def test_inplace_on_second_argument(self):
x, y, z = inputs() x, y, z = inputs()
e0 = x + y e0 = x + y
e1 = tensor.mul_inplace(x, z) e1 = tensor.mul_inplace(x, z)
g = Env([x, y], [e0, e1]) g = Env([x, y], [e0, e1])
assert str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, z)]" self.failUnless(str(g) == "[Broadcast{Add}(x, y), Broadcast{Mul}{0: 0}(x, z)]")
inplace_optimizer.optimize(g) inplace_optimizer.optimize(g)
assert str(g) == "[Broadcast{Add}{0: 1}(x, y), Broadcast{Mul}{0: 0}(x, z)]" self.failUnless(str(g) == "[Broadcast{Add}{0: 1}(x, y), Broadcast{Mul}{0: 0}(x, z)]")
class _test_dimshuffle_lift(unittest.TestCase): class _test_dimshuffle_lift(unittest.TestCase):
...@@ -64,23 +64,23 @@ class _test_dimshuffle_lift(unittest.TestCase): ...@@ -64,23 +64,23 @@ class _test_dimshuffle_lift(unittest.TestCase):
x, y, z = inputs() x, y, z = inputs()
e = ds(ds(x, (1, 0)), (1, 0)) e = ds(ds(x, (1, 0)), (1, 0))
g = Env([x], [e]) g = Env([x], [e])
assert str(g) == "[DimShuffle{10}(DimShuffle{10}(x))]" self.failUnless(str(g) == "[InplaceDimShuffle{1,0}(InplaceDimShuffle{1,0}(x))]")
lift_dimshuffle.optimize(g) lift_dimshuffle.optimize(g)
assert str(g) == "[x]" self.failUnless(str(g) == "[x]")
def test_merge2(self): def test_merge2(self):
x, y, z = inputs() x, y, z = inputs()
e = ds(ds(x, (1, 'x', 0)), (2, 0, 'x', 1)) e = ds(ds(x, (1, 'x', 0)), (2, 0, 'x', 1))
g = Env([x], [e]) g = Env([x], [e])
self.failUnless(str(g) == "[DimShuffle{20x1}(DimShuffle{1x0}(x))]", str(g)) self.failUnless(str(g) == "[InplaceDimShuffle{2,0,x,1}(InplaceDimShuffle{1,x,0}(x))]", str(g))
lift_dimshuffle.optimize(g) lift_dimshuffle.optimize(g)
self.failUnless(str(g) == "[DimShuffle{01xx}(x)]", str(g)) self.failUnless(str(g) == "[InplaceDimShuffle{0,1,x,x}(x)]", str(g))
def test_elim3(self): def test_elim3(self):
x, y, z = inputs() x, y, z = inputs()
e = ds(ds(ds(x, (0, 'x', 1)), (2, 0, 'x', 1)), (1, 0)) e = ds(ds(ds(x, (0, 'x', 1)), (2, 0, 'x', 1)), (1, 0))
g = Env([x], [e]) g = Env([x], [e])
self.failUnless(str(g) == "[DimShuffle{10}(DimShuffle{20x1}(DimShuffle{0x1}(x)))]", str(g)) self.failUnless(str(g) == "[InplaceDimShuffle{1,0}(InplaceDimShuffle{2,0,x,1}(InplaceDimShuffle{0,x,1}(x)))]", str(g))
lift_dimshuffle.optimize(g) lift_dimshuffle.optimize(g)
self.failUnless(str(g) == "[x]", str(g)) self.failUnless(str(g) == "[x]", str(g))
...@@ -88,9 +88,9 @@ class _test_dimshuffle_lift(unittest.TestCase): ...@@ -88,9 +88,9 @@ class _test_dimshuffle_lift(unittest.TestCase):
x, y, z = inputs([0]*1, [0]*2, [0]*3) x, y, z = inputs([0]*1, [0]*2, [0]*3)
e = x + y + z e = x + y + z
g = Env([x, y, z], [e]) g = Env([x, y, z], [e])
self.failUnless(str(g) == "[Broadcast{Add}(DimShuffle{x01}(Broadcast{Add}(DimShuffle{x0}(x), y)), z)]", str(g)) self.failUnless(str(g) == "[Broadcast{Add}(InplaceDimShuffle{x,0,1}(Broadcast{Add}(InplaceDimShuffle{x,0}(x), y)), z)]", str(g))
lift_dimshuffle.optimize(g) lift_dimshuffle.optimize(g)
self.failUnless(str(g) == "[Broadcast{Add}(Broadcast{Add}(DimShuffle{xx0}(x), DimShuffle{x01}(y)), z)]", str(g)) self.failUnless(str(g) == "[Broadcast{Add}(Broadcast{Add}(InplaceDimShuffle{x,x,0}(x), InplaceDimShuffle{x,0,1}(y)), z)]", str(g))
class _test_cliques(unittest.TestCase): class _test_cliques(unittest.TestCase):
...@@ -103,10 +103,10 @@ class _test_cliques(unittest.TestCase): ...@@ -103,10 +103,10 @@ class _test_cliques(unittest.TestCase):
e = x + y + d e = x + y + d
g = Env([x, y, z], [e]) g = Env([x, y, z], [e])
cliques = find_cliques(g) cliques = find_cliques(g)
assert len(cliques) == 2 self.failUnless(len(cliques) == 2)
(i1, o1), (i2, o2) = cliques (i1, o1), (i2, o2) = cliques
assert str(Env(i1, o1)) == "[Broadcast{Add}(Broadcast{Add}(x, y), d)]" self.failUnless(str(Env(i1, o1)) == "[Broadcast{Add}(Broadcast{Add}(x, y), d)]")
assert str(Env(i2, o2)) == "[Broadcast{Mul}(y, z)]" self.failUnless(str(Env(i2, o2)) == "[Broadcast{Mul}(y, z)]")
# print g # print g
# for i, o in find_cliques(g): # for i, o in find_cliques(g):
# print "-->", Env(i, [o]) # print "-->", Env(i, [o])
...@@ -116,8 +116,8 @@ class _test_cliques(unittest.TestCase): ...@@ -116,8 +116,8 @@ class _test_cliques(unittest.TestCase):
e = x + y + z e = x + y + z
g = Env([x, y, z], [e]) g = Env([x, y, z], [e])
lift_dimshuffle.optimize(g) lift_dimshuffle.optimize(g)
assert len(find_cliques(g, through_broadcast = True)) == 1 self.failUnless(len(find_cliques(g, through_broadcast = True)) == 1)
assert len(find_cliques(g, through_broadcast = False)) == 2 self.failUnless(len(find_cliques(g, through_broadcast = False)) == 2)
# print g # print g
# for i, o in find_cliques(g, True): # for i, o in find_cliques(g, True):
# print "-->", Env(i, [o]) # print "-->", Env(i, [o])
......
...@@ -9,6 +9,9 @@ import gof ...@@ -9,6 +9,9 @@ import gof
from gof.python25 import all from gof.python25 import all
# tensor depends on elemwise to provide definitions for several ops
# but elemwise needs to make Tensor instances, so we have these as
# placeholders and the tensor module fills them
def as_tensor(data): def as_tensor(data):
raise Exception("Circular dependencies prevent using this here. import tensor before elemwise") raise Exception("Circular dependencies prevent using this here. import tensor before elemwise")
...@@ -30,10 +33,10 @@ class DimShuffle(Op): ...@@ -30,10 +33,10 @@ class DimShuffle(Op):
""" """
Usage: DimShuffle(new_order, inplace = True) Usage: DimShuffle(new_order, inplace = True)
* new_order: a list representing the relationship between the - new_order: a list representing the relationship between the
input's dimensions and the output's dimensions. Each input's dimensions and the output's dimensions. Each
element of the list can either be an index or 'x'. element of the list can either be an index or 'x'.
* inplace: if True, the output will be a view of the input. - inplace: if True, the output will be a view of the input.
If False, the output will be a copy of the input. If False, the output will be a copy of the input.
If j = new_order[i] is an index, the output's ith dimension If j = new_order[i] is an index, the output's ith dimension
...@@ -47,6 +50,7 @@ class DimShuffle(Op): ...@@ -47,6 +50,7 @@ class DimShuffle(Op):
Examples: Examples:
# t<n> represents a n-d tensor # t<n> represents a n-d tensor
DimShuffle(t0, ['x']) -> make a 0d (scalar) into a 1d vector
DimShuffle(t2, [0, 1]) -> identity DimShuffle(t2, [0, 1]) -> identity
DimShuffle(t2, [1, 0]) -> inverts the first and second dimensions DimShuffle(t2, [1, 0]) -> inverts the first and second dimensions
DimShuffle(t1, ['x', 0]) -> make a row out of a 1d vector DimShuffle(t1, ['x', 0]) -> make a row out of a 1d vector
...@@ -54,6 +58,8 @@ class DimShuffle(Op): ...@@ -54,6 +58,8 @@ class DimShuffle(Op):
DimShuffle(t3, [2, 0, 1]) -> like doing t3.transpose((2, 0, 1)) in numpy DimShuffle(t3, [2, 0, 1]) -> like doing t3.transpose((2, 0, 1)) in numpy
DimShuffle(t2, [0, 'x', 1]) -> like doing t3.reshape((t3.shape[0], 1, t3.shape[1])) in numpy DimShuffle(t2, [0, 'x', 1]) -> like doing t3.reshape((t3.shape[0], 1, t3.shape[1])) in numpy
DimShuffle(t2, [1, 'x', 0]) -> like doing t3.T.reshape((t3.shape[0], 1, t3.shape[1])) in numpy DimShuffle(t2, [1, 'x', 0]) -> like doing t3.T.reshape((t3.shape[0], 1, t3.shape[1])) in numpy
@todo: Default value for inplace should be False! Unsafe optimizations should be explicitly enabled.
""" """
def __init__(self, input_broadcastable, new_order, inplace = True): def __init__(self, input_broadcastable, new_order, inplace = True):
...@@ -113,7 +119,10 @@ class DimShuffle(Op): ...@@ -113,7 +119,10 @@ class DimShuffle(Op):
return hash(self.inplace) ^ hash(self.new_order) ^ hash(self.input_broadcastable) return hash(self.inplace) ^ hash(self.new_order) ^ hash(self.input_broadcastable)
def __str__(self): def __str__(self):
return "DimShuffle{%s}" % "".join(str(x) for x in self.new_order) if self.inplace:
return "InplaceDimShuffle{%s}" % ",".join(str(x) for x in self.new_order)
else:
return "DimShuffle{%s}" % ",".join(str(x) for x in self.new_order)
def perform(self, node, (input, ), (storage, )): def perform(self, node, (input, ), (storage, )):
# drop # drop
......
from collections import deque
import unittest import unittest
from graph import * from graph import *
...@@ -7,6 +7,30 @@ from op import Op ...@@ -7,6 +7,30 @@ from op import Op
from type import Type from type import Type
from graph import Result from graph import Result
def inputs(result_list):
"""
@type result_list: list of L{Result}
@param result_list: output L{Result}s (from which to search backward through owners)
@returns: the list of L{Result}s with no owner, in the order found by a
left-recursive depth-first search started at the L{Result}s in result_list.
"""
def expand(r):
if r.owner:
l = list(r.owner.inputs)
l.reverse()
return l
dfs_results = stack_search(deque(result_list), expand, 'dfs')
rval = [r for r in dfs_results if r.owner is None]
#print rval, _orig_inputs(o)
return rval
if 1:
testcase = unittest.TestCase
else:
testcase = object
realtestcase = unittest.TestCase
class MyType(Type): class MyType(Type):
...@@ -18,10 +42,10 @@ class MyType(Type): ...@@ -18,10 +42,10 @@ class MyType(Type):
return isinstance(other, MyType) and other.thingy == self.thingy return isinstance(other, MyType) and other.thingy == self.thingy
def __str__(self): def __str__(self):
return str(self.thingy) return 'R%s' % str(self.thingy)
def __repr__(self): def __repr__(self):
return str(self.thingy) return 'R%s' % str(self.thingy)
def MyResult(thingy): def MyResult(thingy):
return Result(MyType(thingy), None, None) return Result(MyType(thingy), None, None)
...@@ -75,43 +99,44 @@ MyOp = MyOp() ...@@ -75,43 +99,44 @@ MyOp = MyOp()
# self.outputs = [MyResult(sum([input.thingy for input in inputs]))] # self.outputs = [MyResult(sum([input.thingy for input in inputs]))]
class _test_inputs(unittest.TestCase): class _test_inputs(testcase):
def test_straightforward(self): def test_straightforward(self):
r1, r2 = MyResult(1), MyResult(2) r1, r2 = MyResult(1), MyResult(2)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
assert inputs(node.outputs) == set([r1, r2]) assert inputs(node.outputs) == [r1, r2]
def test_deep(self): def test_deep(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
node2 = MyOp.make_node(node.outputs[0], r5) node2 = MyOp.make_node(node.outputs[0], r5)
assert inputs(node2.outputs) == set([r1, r2, r5]) i = inputs(node2.outputs)
self.failUnless(i == [r1, r2, r5], i)
# def test_unreached_inputs(self): # def test_unreached_inputs(self):
# r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) # r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
# node = MyOp.make_node(r1, r2) # op = MyOp(r1, r2)
# node2 = MyOp.make_node(node.outputs[0], r5) # op2 = MyOp(op.outputs[0], r5)
# try: # try:
# # function doesn't raise if we put False instead of True # # function doesn't raise if we put False instead of True
# ro = results_and_orphans([r1, r2, node2.outputs[0]], node.outputs, True) # ro = results_and_orphans([r1, r2, op2.outputs[0]], op.outputs, True)
# self.fail()
# except Exception, e: # except Exception, e:
# if e[0] is results_and_orphans.E_unreached: # if e[0] is results_and_orphans.E_unreached:
# return # return
# raise # self.fail()
class _test_orphans(unittest.TestCase): class _test_orphans(testcase):
def test_straightforward(self): def test_straightforward(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
node2 = MyOp.make_node(node.outputs[0], r5) node2 = MyOp.make_node(node.outputs[0], r5)
assert orphans([r1, r2], node2.outputs) == set([r5]) orph = orphans([r1, r2], node2.outputs)
self.failUnless(orph == [r5], orph)
class _test_as_string(unittest.TestCase): class _test_as_string(testcase):
leaf_formatter = lambda self, leaf: str(leaf.type) leaf_formatter = lambda self, leaf: str(leaf.type)
node_formatter = lambda self, node, argstrings: "%s(%s)" % (node.op, node_formatter = lambda self, node, argstrings: "%s(%s)" % (node.op,
...@@ -125,29 +150,31 @@ class _test_as_string(unittest.TestCase): ...@@ -125,29 +150,31 @@ class _test_as_string(unittest.TestCase):
def test_straightforward(self): def test_straightforward(self):
r1, r2 = MyResult(1), MyResult(2) r1, r2 = MyResult(1), MyResult(2)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
assert self.str([r1, r2], node.outputs) == ["MyOp(1, 2)"] s = self.str([r1, r2], node.outputs)
self.failUnless(s == ["MyOp(R1, R2)"], s)
def test_deep(self): def test_deep(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
node2 = MyOp.make_node(node.outputs[0], r5) node2 = MyOp.make_node(node.outputs[0], r5)
assert self.str([r1, r2, r5], node2.outputs) == ["MyOp(MyOp(1, 2), 5)"] s = self.str([r1, r2, r5], node2.outputs)
self.failUnless(s == ["MyOp(MyOp(R1, R2), R5)"], s)
def test_multiple_references(self): def test_multiple_references(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
node2 = MyOp.make_node(node.outputs[0], node.outputs[0]) node2 = MyOp.make_node(node.outputs[0], node.outputs[0])
assert self.str([r1, r2, r5], node2.outputs) == ["MyOp(*1 -> MyOp(1, 2), *1)"] assert self.str([r1, r2, r5], node2.outputs) == ["MyOp(*1 -> MyOp(R1, R2), *1)"]
def test_cutoff(self): def test_cutoff(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
node2 = MyOp.make_node(node.outputs[0], node.outputs[0]) node2 = MyOp.make_node(node.outputs[0], node.outputs[0])
assert self.str(node.outputs, node2.outputs) == ["MyOp(3, 3)"] assert self.str(node.outputs, node2.outputs) == ["MyOp(R3, R3)"]
assert self.str(node2.inputs, node2.outputs) == ["MyOp(3, 3)"] assert self.str(node2.inputs, node2.outputs) == ["MyOp(R3, R3)"]
class _test_clone(unittest.TestCase): class _test_clone(testcase):
leaf_formatter = lambda self, leaf: str(leaf.type) leaf_formatter = lambda self, leaf: str(leaf.type)
node_formatter = lambda self, node, argstrings: "%s(%s)" % (node.op, node_formatter = lambda self, node, argstrings: "%s(%s)" % (node.op,
...@@ -162,7 +189,7 @@ class _test_clone(unittest.TestCase): ...@@ -162,7 +189,7 @@ class _test_clone(unittest.TestCase):
r1, r2 = MyResult(1), MyResult(2) r1, r2 = MyResult(1), MyResult(2)
node = MyOp.make_node(r1, r2) node = MyOp.make_node(r1, r2)
_, new = clone([r1, r2], node.outputs, False) _, new = clone([r1, r2], node.outputs, False)
assert self.str([r1, r2], new) == ["MyOp(1, 2)"] assert self.str([r1, r2], new) == ["MyOp(R1, R2)"]
def test_copy(self): def test_copy(self):
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5) r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
...@@ -181,14 +208,89 @@ class _test_clone(unittest.TestCase): ...@@ -181,14 +208,89 @@ class _test_clone(unittest.TestCase):
_, new = clone([r1, r2, r5], node.outputs, False) _, new = clone([r1, r2, r5], node.outputs, False)
new_node = new[0].owner new_node = new[0].owner
new_node.inputs = MyResult(7), MyResult(8) new_node.inputs = MyResult(7), MyResult(8)
assert self.str(inputs(new_node.outputs), new_node.outputs) == ["MyOp(R7, R8)"]
assert self.str(inputs(node.outputs), node.outputs) == ["MyOp(MyOp(R1, R2), R5)"]
def prenode(obj):
if isinstance(obj, Result):
if obj.owner:
return [obj.owner]
if isinstance(obj, Op):
return obj.inputs
class _test_toposort(testcase):
def test0(self):
"""Test a simple graph"""
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
o = MyOp(r1, r2)
o2 = MyOp(o.outputs[0], r5)
all = general_toposort(o2.outputs, prenode)
self.failUnless(all == [r5, r2, r1, o, o.outputs[0], o2, o2.outputs[0]], all)
all = io_toposort([r5], o2.outputs)
self.failUnless(all == [o, o2], all)
assert self.str(inputs(new_node.outputs), new_node.outputs) == ["MyOp(7, 8)"] def test1(self):
assert self.str(inputs(node.outputs), node.outputs) == ["MyOp(MyOp(1, 2), 5)"] """Test a graph with double dependencies"""
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
o = MyOp(r1, r1)
o2 = MyOp(o.outputs[0], r5)
all = general_toposort(o2.outputs, prenode)
self.failUnless(all == [r5, r1, o, o.outputs[0], o2, o2.outputs[0]], all)
def test2(self):
"""Test a graph where the inputs have owners"""
r1, r2, r5 = MyResult(1), MyResult(2), MyResult(5)
o = MyOp(r1, r1)
r2b = o.outputs[0]
o2 = MyOp(r2b, r2b)
all = io_toposort([r2b], o2.outputs)
self.failUnless(all == [o2], all)
o2 = MyOp(r2b, r5)
all = io_toposort([r2b], o2.outputs)
self.failUnless(all == [o2], all)
def test3(self):
"""Test a graph which is not connected"""
r1, r2, r3, r4 = MyResult(1), MyResult(2), MyResult(3), MyResult(4)
o0 = MyOp(r1, r2)
o1 = MyOp(r3, r4)
all = io_toposort([r1, r2, r3, r4], o0.outputs + o1.outputs)
self.failUnless(all == [o1,o0], all)
def test4(self):
"""Test inputs and outputs mixed together in a chain graph"""
r1, r2, r3, r4 = MyResult(1), MyResult(2), MyResult(3), MyResult(4)
o0 = MyOp(r1, r2)
o1 = MyOp(o0.outputs[0], r1)
all = io_toposort([r1, o0.outputs[0]], [o0.outputs[0], o1.outputs[0]])
self.failUnless(all == [o1], all)
def test5(self):
"""Test when outputs have clients"""
r1, r2, r3, r4 = MyResult(1), MyResult(2), MyResult(3), MyResult(4)
o0 = MyOp(r1, r2)
o1 = MyOp(o0.outputs[0], r4)
all = io_toposort([], o0.outputs)
self.failUnless(all == [o0], all)
if __name__ == '__main__': if __name__ == '__main__':
if 1:
#run all tests
unittest.main() unittest.main()
elif 1:
#load some TestCase classes
suite = unittest.TestLoader()
suite = suite.loadTestsFromTestCase(_test_toposort)
#run just some of them
unittest.TextTestRunner(verbosity=2).run(suite)
else:
#run just a single test
_test_toposort('test0').debug()
from copy import copy from copy import copy
from collections import deque
import utils import utils
from utils import object2 from utils import object2
...@@ -161,8 +162,6 @@ def as_apply(x): ...@@ -161,8 +162,6 @@ def as_apply(x):
else: else:
raise TypeError("Cannot map %s to Apply" % x) raise TypeError("Cannot map %s to Apply" % x)
@deprecated @deprecated
def inputs(o): def inputs(o):
""" """
...@@ -184,55 +183,105 @@ def inputs(o): ...@@ -184,55 +183,105 @@ def inputs(o):
seek(output) seek(output)
return results return results
def stack_search(start, expand, mode='bfs', build_inv = False):
"""Search through L{Result}s, either breadth- or depth-first
@type start: deque
@param start: search from these nodes
@type explore: function
@param explore: when we get to a node, add explore(node) to the list of
nodes to visit. This function should return a list, or None
@rtype: list of L{Result}
@return: the list of L{Result}s in order of traversal.
@note: a L{Result} will appear at most once in the return value, even if it
appears multiple times in the start parameter.
@postcondition: every element of start is transferred to the returned list.
@postcondition: start is empty.
"""
# def results_and_orphans(i, o, except_unreachable_input=False): if mode not in ('bfs', 'dfs'):
# """ raise ValueError('mode should be bfs or dfs', mode)
# @type i: list rval_set = set()
# @param i: input L{Result}s rval_list = list()
# @type o: list start_pop = start.popleft if mode is 'bfs' else start.pop
# @param o: output L{Result}s expand_inv = {}
while start:
# Returns the pair (results, orphans). The former is the set of l = start_pop()
# L{Result}s that are involved in the subgraph that lies between i and if id(l) not in rval_set:
# o. This includes i, o, orphans(i, o) and all results of all rval_list.append(l)
# intermediary steps from i to o. The second element of the returned rval_set.add(id(l))
# pair is orphans(i, o). expand_l = expand(l)
# """ if expand_l:
# results = set() if build_inv:
# i = set(i) for r in expand_l:
# # results.update(i) expand_inv.setdefault(r, []).append(l)
# incomplete_paths = [] start.extend(expand_l)
# reached = set() assert len(rval_list) == len(rval_set)
if build_inv:
# def helper(r, path): return rval_list, expand_inv
# if r in i: return rval_list
# reached.add(r)
# results.update(path)
# elif r.owner is None: @utils.deprecated('gof.graph', 'is this function ever used?')
# incomplete_paths.append(path) def inputs(result_list):
# else: """
# op = r.owner @type result_list: list of L{Result}
# for r2 in op.inputs: @param result_list: output L{Result}s (from which to search backward through owners)
# helper(r2, path + [r2]) @returns: the list of L{Result}s with no owner, in the order found by a
left-recursive depth-first search started at the L{Result}s in result_list.
# for output in o:
# helper(output, [output])
# orphans = set()
# for path in incomplete_paths:
# for r in path:
# if r not in results:
# orphans.add(r)
# break
# if except_unreachable_input and len(i) != len(reached):
# raise Exception(results_and_orphans.E_unreached)
# results.update(orphans)
"""
def expand(r):
if r.owner:
l = list(r.owner.inputs)
l.reverse()
return l
dfs_results = stack_search(deque(result_list), expand, 'dfs')
rval = [r for r in dfs_results if r.owner is None]
#print rval, _orig_inputs(o)
return rval
# def results_and_orphans(r_in, r_out, except_unreachable_input=False):
# r_in_set = set(r_in)
# class Dummy(object): pass
# dummy = Dummy()
# dummy.inputs = r_out
# def expand_inputs(io):
# if io in r_in_set:
# return None
# try:
# return [io.owner] if io.owner != None else None
# except AttributeError:
# return io.inputs
# ops_and_results, dfsinv = stack_search(
# deque([dummy]),
# expand_inputs, 'dfs', True)
# if except_unreachable_input:
# for r in r_in:
# if r not in dfsinv:
# raise Exception(results_and_orphans.E_unreached)
# clients = stack_search(
# deque(r_in),
# lambda io: dfsinv.get(io,None), 'dfs')
# ops_to_compute = [o for o in clients if is_op(o) and o is not dummy]
# results = []
# for o in ops_to_compute:
# results.extend(o.inputs)
# results.extend(r_out)
# op_set = set(ops_to_compute)
# assert len(ops_to_compute) == len(op_set)
# orphans = [r for r in results \
# if (r.owner not in op_set) and (r not in r_in_set)]
# return results, orphans # return results, orphans
# results_and_orphans.E_unreached = 'there were unreachable inputs' # results_and_orphans.E_unreached = 'there were unreachable inputs'
def results_and_orphans(i, o): def results_and_orphans(i, o):
results = set() results = set()
orphans = set() orphans = set()
...@@ -251,7 +300,6 @@ def results_and_orphans(i, o): ...@@ -251,7 +300,6 @@ def results_and_orphans(i, o):
return results, orphans return results, orphans
def ops(i, o): def ops(i, o):
""" """
@type i: list @type i: list
...@@ -370,61 +418,70 @@ def clone_get_equiv(i, o, copy_inputs_and_orphans = False): ...@@ -370,61 +418,70 @@ def clone_get_equiv(i, o, copy_inputs_and_orphans = False):
return d return d
# d = {} def general_toposort(r_out, deps):
"""
# for input in i: @note: deps(i) should behave like a pure function (no funny business with
# if copy_inputs_and_orphans: internal state)
# d[input] = copy(input)
# else: @note: deps(i) can/should be cached by the deps function to be fast
# d[input] = input """
deps_cache = {}
# def clone_helper(result): def _deps(io):
# if result in d: if io not in deps_cache:
# return d[result] d = deps(io)
# op = result.owner if d:
# if not op: # result is an orphan deps_cache[io] = list(d)
# if copy_inputs_and_orphans: else:
# d[result] = copy(result) deps_cache[io] = d
# else: return d
# d[result] = result else:
# return d[result] return deps_cache[io]
# else:
# new_op = op.clone_with_new_inputs(*[clone_helper(input) for input in op.inputs]) assert isinstance(r_out, (tuple, list, deque))
# d[op] = new_op
# for output, new_output in zip(op.outputs, new_op.outputs): reachable, clients = stack_search( deque(r_out), _deps, 'dfs', True)
# d[output] = new_output sources = deque([r for r in reachable if not deps_cache.get(r, None)])
# return d[result]
rset = set()
# for output in o: rlist = []
# clone_helper(output) while sources:
node = sources.popleft()
# return d if node not in rset:
rlist.append(node)
rset.add(node)
for client in clients.get(node, []):
deps_cache[client] = [a for a in deps_cache[client] if a is not node]
if not deps_cache[client]:
sources.append(client)
if len(rlist) != len(reachable):
print ''
print reachable
print rlist
raise 'failed to complete topological sort of given nodes'
return rlist
def io_toposort(i, o, orderings = {}): def io_toposort(i, o, orderings = {}):
""" iset = set(i)
@type i: list def deps(obj):
@param i: input L{Result}s rval = []
@type o: list if obj not in iset:
@param o: output L{Result}s if isinstance(obj, result.Result):
@param orderings: {op: [requirements for op]} (defaults to {}) if obj.owner:
rval = [obj.owner]
if isinstance(obj, op.Op):
rval = list(obj.inputs)
rval.extend(orderings.get(obj, []))
else:
assert not orderings.get(obj, [])
return rval
topo = general_toposort(o, deps)
return [o for o in topo if isinstance(o, op.Op)]
@rtype: ordered list
@return: L{Op}s that belong in the subgraph between i and o which
respects the following constraints:
- all inputs in i are assumed to be already computed
- the L{Op}s that compute an L{Op}'s inputs must be computed before it
- the orderings specified in the optional orderings parameter must be satisfied
Note that this function does not take into account ordering information
related to destructive operations or other special behavior.
"""
prereqs_d = copy(orderings)
all = ops(i, o)
for op in all:
asdf = set([input.owner for input in op.inputs if input.owner and input.owner in all])
prereqs_d.setdefault(op, set()).update(asdf)
return utils.toposort(prereqs_d)
default_leaf_formatter = str default_leaf_formatter = str
...@@ -459,6 +516,8 @@ def as_string(i, o, ...@@ -459,6 +516,8 @@ def as_string(i, o,
exist for viewing convenience). exist for viewing convenience).
""" """
i = set(i)
orph = orphans(i, o) orph = orphans(i, o)
multi = set() multi = set()
...@@ -546,4 +605,82 @@ class Graph: ...@@ -546,4 +605,82 @@ class Graph:
if 0:
#these were the old implementations
# they were replaced out of a desire that graph search routines would not
# depend on the hash or id of any node, so that it would be deterministic
# and consistent between program executions.
@utils.deprecated('gof.graph', 'preserving only for review')
def _results_and_orphans(i, o, except_unreachable_input=False):
"""
@type i: list
@param i: input L{Result}s
@type o: list
@param o: output L{Result}s
Returns the pair (results, orphans). The former is the set of
L{Result}s that are involved in the subgraph that lies between i and
o. This includes i, o, orphans(i, o) and all results of all
intermediary steps from i to o. The second element of the returned
pair is orphans(i, o).
"""
results = set()
i = set(i)
results.update(i)
incomplete_paths = []
reached = set()
def helper(r, path):
if r in i:
reached.add(r)
results.update(path)
elif r.owner is None:
incomplete_paths.append(path)
else:
op = r.owner
for r2 in op.inputs:
helper(r2, path + [r2])
for output in o:
helper(output, [output])
orphans = set()
for path in incomplete_paths:
for r in path:
if r not in results:
orphans.add(r)
break
if except_unreachable_input and len(i) != len(reached):
raise Exception(results_and_orphans.E_unreached)
results.update(orphans)
return results, orphans
def _io_toposort(i, o, orderings = {}):
"""
@type i: list
@param i: input L{Result}s
@type o: list
@param o: output L{Result}s
@param orderings: {op: [requirements for op]} (defaults to {})
@rtype: ordered list
@return: L{Op}s that belong in the subgraph between i and o which
respects the following constraints:
- all inputs in i are assumed to be already computed
- the L{Op}s that compute an L{Op}'s inputs must be computed before it
- the orderings specified in the optional orderings parameter must be satisfied
Note that this function does not take into account ordering information
related to destructive operations or other special behavior.
"""
prereqs_d = copy(orderings)
all = ops(i, o)
for op in all:
asdf = set([input.owner for input in op.inputs if input.owner and input.owner in all])
prereqs_d.setdefault(op, set()).update(asdf)
return utils.toposort(prereqs_d)
...@@ -35,13 +35,14 @@ class Op(object2): ...@@ -35,13 +35,14 @@ class Op(object2):
# Python implementation # # Python implementation #
######################### #########################
def impl(self, node, inputs, output_storage): def perform(self, node, inputs, output_storage):
""" """
Calculate the function on the inputs and put the results in the Calculate the function on the inputs and put the results in the
output storage. output storage.
- inputs: sequence of inputs (immutable) - inputs: sequence of inputs (immutable)
- outputs: mutable list - output_storage: list of mutable 1-element lists (do not change
the length of these lists)
The output_storage list might contain data. If an element of The output_storage list might contain data. If an element of
output_storage is not None, it is guaranteed that it was produced output_storage is not None, it is guaranteed that it was produced
...@@ -50,36 +51,10 @@ class Op(object2): ...@@ -50,36 +51,10 @@ class Op(object2):
""" """
raise AbstractFunctionError() raise AbstractFunctionError()
##################### #####################
# C code generation # # C code generation #
##################### #####################
# def c_validate_update(self, inputs, outputs, sub):
# """
# Returns templated C code that checks that the inputs to this
# function can be worked on. If a failure occurs, set an
# Exception and insert "%(fail)s".
# You may use the variable names defined by c_var_names() in
# the template.
# Note: deprecated!!
# @todo: Merge this with c_code.
# """
# raise AbstractFunctionError()
# def c_validate_update_cleanup(self, inputs, outputs, sub):
# """
# Clean up things allocated by L{c_validate}().
# Note: deprecated!!
# @todo: Merge this with c_code.
# """
# raise AbstractFunctionError()
# raise AbstractFunctionError('%s.c_validate_update_cleanup ' \
# % self.__class__.__name__)
def c_code(self, node, name, inputs, outputs, sub): def c_code(self, node, name, inputs, outputs, sub):
"""Return the C implementation of an Op. """Return the C implementation of an Op.
...@@ -151,28 +126,3 @@ class PropertiedOp(Op): ...@@ -151,28 +126,3 @@ class PropertiedOp(Op):
return "%s{%s}" % (self.__class__.__name__, ", ".join("%s=%s" % (k, v) for k, v in self.__dict__.items() if k != "name")) return "%s{%s}" % (self.__class__.__name__, ", ".join("%s=%s" % (k, v) for k, v in self.__dict__.items() if k != "name"))
# #TODO: consider adding a flag to the base class that toggles this behaviour
# class GuardedOp(Op):
# """An Op that disallows input properties to change after construction"""
# def set_input(self, i, new):
# old = self._inputs[i]
# if old is new:
# return
# try:
# if not old.same_properties(new):
# raise TypeError("The new input must have the same properties as the previous one.")
# except AbstractFunctionError:
# pass
# Op.set_input(self, i, new)
# def set_inputs(self, new):
# if not hasattr(self, '_inputs') or self_inputs is None:
# Op.set_inputs(self, new)
# else:
# if not len(new) == len(self._inputs):
# raise TypeError("The new inputs are not as many as the previous ones.")
# for i, new in enumerate(new):
# self.set_input(i, new)
...@@ -38,6 +38,31 @@ class scratchpad: ...@@ -38,6 +38,31 @@ class scratchpad:
def deprecated(filename, msg=''):
"""Decorator which will print a warning message on the first call.
Use it like this:
@deprecated('myfile', 'do something different...')
def fn_name(...)
...
And it will print
WARNING myfile.fn_name deprecated. do something different...
"""
def _deprecated(f):
printme = [True]
def g(*args, **kwargs):
if printme[0]:
print 'WARNING: %s.%s deprecated. %s'\
% (filename, f.__name__, msg)
printme[0] = False
return f(*args, **kwargs)
return g
return _deprecated
def uniq(seq): def uniq(seq):
#TODO: consider building a set out of seq so that the if condition is constant time -JB #TODO: consider building a set out of seq so that the if condition is constant time -JB
return [x for i, x in enumerate(seq) if seq.index(x) == i] return [x for i, x in enumerate(seq) if seq.index(x) == i]
...@@ -55,6 +80,7 @@ def difference(seq1, seq2): ...@@ -55,6 +80,7 @@ def difference(seq1, seq2):
# -> use O(len(seq1) * len(seq2)) algo # -> use O(len(seq1) * len(seq2)) algo
return [x for x in seq1 if x not in seq2] return [x for x in seq1 if x not in seq2]
def partition(f, seq): def partition(f, seq):
seqt = [] seqt = []
seqf = [] seqf = []
......
...@@ -368,368 +368,3 @@ def dot(x, y, grad_preserves_dense=True): ...@@ -368,368 +368,3 @@ def dot(x, y, grad_preserves_dense=True):
else: else:
assert y_is_sparse_result assert y_is_sparse_result
return transpose(Dot(grad_preserves_dense)(y.T, x.T)) return transpose(Dot(grad_preserves_dense)(y.T, x.T))
# """
# Classes for handling sparse matrices.
# To read about different sparse formats, see U{http://www-users.cs.umn.edu/~saad/software/SPARSKIT/paper.ps}.
# @todo: Automatic methods for determining best sparse format?
# """
# import copy #for __copy__
# import numpy
# from scipy import sparse
# import gof.op, gof.result
# import tensor
# """ Types of sparse matrices to use for testing """
# _mtypes = [sparse.csc_matrix, sparse.csr_matrix]
# #_mtypes = [sparse.csc_matrix, sparse.csr_matrix, sparse.dok_matrix, sparse.lil_matrix, sparse.coo_matrix]
# _mtype_to_str = {sparse.csc_matrix: "csc", sparse.csr_matrix: "csr"}
# ## Type checking
# def _is_sparse_result(x):
# """
# @rtype: boolean
# @return: True iff x is a L{SparseResult} (and not a L{tensor.Tensor})
# """
# if not isinstance(x, SparseResult) and not isinstance(x, tensor.Tensor):
# raise NotImplementedError("_is_sparse should only be called on sparse.SparseResult or tensor.Tensor, not,", x)
# return isinstance(x, SparseResult)
# def _is_dense_result(x):
# """
# @rtype: boolean
# @return: True unless x is a L{SparseResult} (and not a L{tensor.Tensor})
# """
# if not isinstance(x, SparseResult) and not isinstance(x, tensor.Tensor):
# raise NotImplementedError("_is_sparse should only be called on sparse.SparseResult or tensor.Tensor, not,", x)
# return isinstance(x, tensor.Tensor)
# def _is_sparse(x):
# """
# @rtype: boolean
# @return: True iff x is a L{scipy.sparse.spmatrix} (and not a L{numpy.ndarray})
# """
# if not isinstance(x, sparse.spmatrix) and not isinstance(x, numpy.ndarray):
# raise NotImplementedError("_is_sparse should only be called on sparse.scipy.sparse.spmatrix or numpy.ndarray, not,", x)
# return isinstance(x, sparse.spmatrix)
# def _is_dense(x):
# """
# @rtype: boolean
# @return: True unless x is a L{scipy.sparse.spmatrix} (and not a L{numpy.ndarray})
# """
# if not isinstance(x, sparse.spmatrix) and not isinstance(x, numpy.ndarray):
# raise NotImplementedError("_is_sparse should only be called on sparse.scipy.sparse.spmatrix or numpy.ndarray, not,", x)
# return isinstance(x, numpy.ndarray)
# # Wrapper type
# def assparse(sp, **kwargs):
# """
# Wrapper around SparseResult constructor.
# @param sp: A sparse matrix. assparse reads dtype and format properties
# out of this sparse matrix.
# @return: SparseResult version of sp.
# @todo Verify that sp is sufficiently sparse, and raise a warning if it is not
# """
# if isinstance(sp, SparseResult):
# rval = sp
# else:
# # @todo Verify that sp is sufficiently sparse, and raise a
# # warning if it is not
# rval = SparseResult(str(sp.dtype), sp.format, **kwargs)
# rval.data = sp
# assert _is_sparse_result(rval)
# return rval
# class SparseResult(gof.result.Result):
# """
# @type _dtype: numpy dtype string such as 'int64' or 'float64' (among others)
# @type _format: string
# @ivar _format: The sparse storage strategy.
# @note As far as I can tell, L{scipy.sparse} objects must be matrices, i.e. have dimension 2.
# """
# format_cls = {
# 'csr' : sparse.csr_matrix,
# 'csc' : sparse.csc_matrix
# }
# dtype_set = set(['int', 'int32', 'int64', 'float32', 'float64'])
# def __init__(self, dtype, format, **kwargs):
# """
# Fundamental way to create a sparse node.
# @param dtype: Type of numbers in the matrix.
# @param format: The sparse storage strategy.
# @return An empty SparseResult instance.
# """
# gof.Result.__init__(self, **kwargs)
# if dtype in SparseResult.dtype_set:
# self._dtype = dtype
# assert isinstance(format, str)
# #print format, type(format), SparseResult.format_cls.keys(), format in SparseResult.format_cls
# if format in SparseResult.format_cls:
# self._format = format
# else:
# raise NotImplementedError('unsupported format "%s" not in list' % format, SparseResult.format_cls.keys())
# def filter(self, value):
# if isinstance(value, SparseResult.format_cls[self.format])\
# and value.dtype == self.dtype:
# return value
# #print 'pass-through failed', type(value)
# sp = SparseResult.format_cls[self.format](value)
# if str(sp.dtype) != self.dtype:
# raise NotImplementedError()
# if sp.format != self.format:
# raise NotImplementedError()
# return sp
# def __copy__(self):
# if self.name is not None:
# rval = SparseResult(self._dtype, self._format, name=self.name)
# else:
# rval = SparseResult(self._dtype, self._format)
# rval.data = copy.copy(self.data)
# return rval
# dtype = property(lambda self: self._dtype)
# format = property(lambda self: self._format)
# T = property(lambda self: transpose(self), doc = "Return aliased transpose of self (read-only)")
# def __add__(left, right): return add(left, right)
# def __radd__(right, left): return add(left, right)
# #
# # Conversion
# #
# # convert a sparse matrix to an ndarray
# class DenseFromSparse(gof.op.Op):
# def __init__(self, x, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# self.inputs = [assparse(x)]
# self.outputs = [tensor.Tensor(x.dtype,[0,0])]
# def impl(self, x):
# assert _is_sparse(x)
# return numpy.asarray(x.todense())
# def grad(self, (x,), (gz,)):
# assert _is_sparse_result(x) and _is_dense_result(gz)
# return sparse_from_dense(gz, x.format),
# dense_from_sparse = gof.op.constructor(DenseFromSparse)
# class SparseFromDense(gof.op.Op):
# def __init__(self, x, format, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# if isinstance(format, gof.result.Result):
# self.inputs = [tensor.astensor(x), format]
# else:
# self.inputs = [tensor.astensor(x), gof.result.PythonResult()]
# self.inputs[1].data = format
# self.outputs = [SparseResult(x.dtype, self.inputs[1].data)]
# def impl(self, x, fmt):
# # this would actually happen anyway when we try to assign to
# # self.outputs[0].data, but that seems hackish -JB
# assert _is_dense(x)
# return SparseResult.format_cls[fmt](x)
# def grad(self, (x, fmt), (gz,)):
# assert _is_dense_result(x) and _is_sparse_result(gz)
# return dense_from_sparse(gz), None
# sparse_from_dense = gof.op.constructor(SparseFromDense)
# # Linear Algebra
# class Transpose(gof.op.Op):
# format_map = {
# 'csr' : 'csc',
# 'csc' : 'csr'}
# def __init__(self, x, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# x = assparse(x)
# self.inputs = [x]
# self.outputs = [SparseResult(x.dtype, Transpose.format_map[x.format])]
# def impl(self, x):
# assert _is_sparse(x)
# return x.transpose()
# def grad(self, (x,), (gz,)):
# assert _is_sparse_result(x) and _is_sparse_result(gz)
# return transpose(gz),
# transpose = gof.op.constructor(Transpose)
# class AddSS(gof.op.Op):
# ''' Add two sparse matrices '''
# def __init__(self, x, y, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# x, y = [assparse(x), assparse(y)]
# self.inputs = [x, y]
# if x.dtype != y.dtype:
# raise NotImplementedError()
# if x.format != y.format:
# raise NotImplementedError()
# self.outputs = [SparseResult(x.dtype, x.format)]
# def impl(self, x,y):
# assert _is_sparse(x) and _is_sparse(y)
# return x + y
# def grad(self, (x, y), (gz,)):
# assert _is_sparse_result(x) and _is_sparse_result(y)
# assert _is_sparse_result(gz)
# return gz, gz
# add_s_s = gof.op.constructor(AddSS)
# class AddSD(gof.op.Op):
# ''' Add a sparse and a dense matrix '''
# def __init__(self, x, y, **kwargs):
# gof.op.Op.__init__(self, **kwargs)
# x, y = [assparse(x), tensor.astensor(y)]
# self.inputs = [x, y]
# if x.dtype != y.dtype:
# raise NotImplementedError()
# # The magic number two here arises because L{scipy.sparse}
# # objects must be matrices (have dimension 2)
# assert len(y.broadcastable) == 2
# self.outputs = [tensor.Tensor(y.dtype, y.broadcastable)]
# def impl(self, x,y):
# assert _is_sparse(x) and _is_dense(y)
# return x + y
# def grad(self, (x, y), (gz,)):
# assert _is_sparse_result(x) and _is_dense_result(y)
# assert _is_dense_result(gz)
# return SparseFromDense(gz), gz
# add_s_d = gof.op.constructor(AddSD)
# def add(x,y):
# """
# Add two matrices, at least one of which is sparse.
# """
# if hasattr(x, 'getnnz'): x = assparse(x)
# if hasattr(y, 'getnnz'): y = assparse(y)
# x_is_sparse_result = _is_sparse_result(x)
# y_is_sparse_result = _is_sparse_result(y)
# assert x_is_sparse_result or y_is_sparse_result
# if x_is_sparse_result and y_is_sparse_result: return add_s_s(x,y)
# elif x_is_sparse_result and not y_is_sparse_result: return add_s_d(x,y)
# elif y_is_sparse_result and not x_is_sparse_result: return add_s_d(y,x)
# else: raise NotImplementedError()
# class Dot(gof.op.Op):
# """
# Attributes:
# grad_preserves_dense - a boolean flags [default: True].
# grad_preserves_dense controls whether gradients with respect to inputs
# are converted to dense matrices when the corresponding input y is
# dense (not in a L{SparseResult} wrapper). This is generally a good idea
# when L{Dot} is in the middle of a larger graph, because the types
# of gy will match that of y. This conversion might be inefficient if
# the gradients are graph outputs though, hence this mask.
# @todo: Simplify code by splitting into DotSS and DotSD.
# """
# def __init__(self, x, y, grad_preserves_dense=True):
# """
# Because of trickiness of implementing, we assume that the left argument x is SparseResult (not dense)
# """
# if x.dtype != y.dtype:
# raise NotImplementedError()
# assert _is_sparse_result(x)
# # These are the conversions performed by scipy.sparse.dot
# if x.format == "csc" or x.format == "coo":
# myformat = "csc"
# elif x.format == "csr":
# myformat = "csr"
# else:
# raise NotImplementedError()
# self.inputs = [x, y] # Need to convert? e.g. assparse
# self.outputs = [SparseResult(x.dtype, myformat)]
# self.grad_preserves_dense = grad_preserves_dense
# def perform(self):
# """
# @todo: Verify that output is sufficiently sparse, and raise a warning if it is not
# @todo: Also determine that we are storing the output in the best storage format?
# """
# self.outputs[0].data = self.inputs[0].data.dot(self.inputs[1].data)
# def grad(self, (x, y), (gz,)):
# assert _is_sparse_result(gz)
# rval = [dot(gz, y.T), dot(x.T, gz)]
# assert _is_sparse_result(x)
# if _is_dense_result(y):
# if self.grad_preserves_dense:
# rval[1] = dense_from_sparse(rval[1])
# return rval
# def __copy__(self):
# return self.__class__(self.inputs[0], self.inputs[1], self.grad_preserves_dense)
# def clone_with_new_inputs(self, *new_inputs):
# return self.__class__(new_inputs[0], new_inputs[1], self.grad_preserves_dense)
# def dot(x, y, grad_preserves_dense=True):
# """
# @todo: Maybe the triple-transposition formulation (when x is dense)
# is slow. See if there is a direct way to do this.
# """
# if hasattr(x, 'getnnz'): x = assparse(x)
# if hasattr(y, 'getnnz'): y = assparse(y)
# x_is_sparse_result = _is_sparse_result(x)
# y_is_sparse_result = _is_sparse_result(y)
# if not x_is_sparse_result and not y_is_sparse_result:
# raise TypeError()
# if x_is_sparse_result:
# return Dot(x, y, grad_preserves_dense).outputs[0]
# else:
# assert y_is_sparse_result
# return transpose(Dot(y.T, x.T, grad_preserves_dense).outputs[0])
...@@ -334,8 +334,10 @@ class _tensor_py_operators: ...@@ -334,8 +334,10 @@ class _tensor_py_operators:
T = property(lambda self: transpose(self)) T = property(lambda self: transpose(self))
#SLICING #SLICING
def __getitem__(self, item): return subtensor(self, item) def __getitem__(self, args): return Subtensor.from_idxs(self,
def __getslice__(self, *args): return subtensor(self, slice(*args)) args).outputs[0]
def __getslice__(self, *args): return Subtensor.from_idxs(self,
(slice(*args),)).outputs[0]
#COPYING #COPYING
def copy(self): return tensor_copy(self) def copy(self): return tensor_copy(self)
...@@ -356,15 +358,43 @@ s2t.TensorConstant = TensorConstant ...@@ -356,15 +358,43 @@ s2t.TensorConstant = TensorConstant
s2t.TensorValue = TensorValue s2t.TensorValue = TensorValue
#########################
# Casting Operations
#########################
class TensorFromScalar(Op):
def make_node(self, s):
assert isinstance(s.type, scal.Scalar)
return Apply(self,
[s],
[tensor(dtype = s.type.dtype,
broadcastable = ())])
def perform(self, node, (s, ), (out, )):
out[0] = numpy.asarray(s)
def grad(self, (s,), (dt,)):
raise NotImplementedError('todo: ScalarFromTensor')
tensor_from_scalar = TensorFromScalar()
############################
# Supporting Ops
############################
########################## ##########################
# Unary Operations # Unary Operations
########################## ##########################
class Shape(Op):
"""
L{Op} to return the shape of a matrix.
@note: Non-differentiable.
"""
def make_node(self, x):
x = as_tensor(x)
return Apply(self, [x], [ivector()])
def perform(self, node, (x, ), (out, )):
out[0] = numpy.asarray(x.shape)
def grad(self, (x,), (gz,)):
raise ValueError
shape = Shape()
class Argmax(Op): class Argmax(Op):
"""Calculate the max and argmax over a given axis""" """Calculate the max and argmax over a given axis"""
nin=2 # tensor, axis nin=2 # tensor, axis
...@@ -470,50 +500,223 @@ transpose_inplace = TransposeInplace() ...@@ -470,50 +500,223 @@ transpose_inplace = TransposeInplace()
def transpose(x, **kwargs): def transpose(x, **kwargs):
return transpose_inplace(tensor_copy(x), **kwargs) return transpose_inplace(tensor_copy(x), **kwargs)
# class Subtensor(Op): class Subtensor_dx(Op, Viewer):
# nin = 2 """Return a tensor full of zeros, except for what was sliced from x by
# nout = 1 Subtensor.
# e_invalid = 'invalid index'
# view_map = {0: [0]} @todo: pass the shape of x, rather than x itself.
# def make_node(self, *inputs):
# def as_tuple_result(obj): @todo: add support for advanced tensor indexing (breaks current perform
# if isinstance(obj, gof.Result): implementation).
# return obj """
# assert isinstance(obj, (list, tuple)) def __init__(self, inputs, idx_list, **kwargs):
# r = gof.Constant(gof.generic, obj) Op.__init__(self, **kwargs)
# return r self.inputs = inputs
# # def pad(tplR, N): self.outputs = [Tensor(inputs[0].dtype, inputs[0].broadcastable)]
# # l = list(tplR.data) self.idx_list = idx_list
# # for i in range(len(l), N):
# # l.append(slice(0,sys.maxint,1)) def perform(self):
# # tplR.data = tuple(l) x = self.inputs[0]
gz = self.inputs[-1]
# t, coord = args cdata = []
# t = _as_tensor(t) for c in self.idx_list:
# coord = as_tuple_result(coord) if isinstance(c, slice):
# if len(coord.data) > len(t.broadcastable): cdata.append(slice(
# raise ValueError(Subtensor.e_invalid) None if c.start is None else self.inputs[c.start].data,
# # add the implicit extra unbounded slices None if c.stop is None else self.inputs[c.stop].data,
# # e.g. n[0] on a 3d tensor pads to n[0,:,:] None if c.step is None else self.inputs[c.step].data))
# ###pad(coord, len(t.broadcastable)) else:
# broadcastable = [False for c in coord.data if isinstance(c, slice)] d = self.inputs[c].data
# self.inputs = [t, coord] assert 'int' in str(d.dtype)
# self.outputs = [Tensor(t.dtype, broadcastable)] cdata.append(d)
# def view_map(self): if len(cdata) > 1:
# return {self.out: [self.inputs[0]]} cdata = tuple(cdata) #there's a diff between tuple and list here...
# def perform(self, node, (x, c), (out, )): else:
# if len(c) == 1: cdata = cdata[0]
# out[0] = x.__getitem__(c[0])
# else: #print cdata
# out[0] = x.__getitem__(c) #print gz.data
# def grad(self, (x,), (gz,)): gx = numpy.zeros_like(x.data)
# # - option: allocate a potentially large matrix of zeros, and fill in gx[cdata] = gz.data
# # the appropriate elements from gz #print gx
# # - option: return a sparse matrix
# # - option: return gz, but think about how to include a special addition self.outputs[0].data = gx
# # function that works on a corresponding view of the original data
# raise NotImplementedError() def clone_with_new_inputs(self, *new_inputs):
# subtensor = Subtensor() assert len(self.inputs) == len(new_inputs)
return Subtensor_dx(new_inputs, self.idx_list)
class Subtensor(Op, Viewer):
"""Return a subtensor view
This class uses a relatively complex internal representation of the inputs
to remember how the input tensor x should be sliced. The instance variable
idxlist is a list whose elements are either integers, or slices. The
integers are indexes into the inputs array, and the start/stop/step members
of each slice are also integer indexes into the inputs array (or None). The
inputs array is the tensor x, followed by scalar integer results.
@todo: add support for advanced tensor indexing (in Subtensor_dx too).
"""
e_invalid = 'invalid index'
debug = 0
@staticmethod
def from_idxs(x, idxs, **kwargs):
if Subtensor.debug:
print idxs, sys.maxint
def asidx(i):
if isinstance(i, int): return scal.constant(i)
if isinstance(i, scal.Scalar) and ('int' in i.dtype): return i
raise TypeError(Subtensor.e_invalid, i)
x = _as_tensor(x)
idx_list = [] # like args, but with int -> scalar.constant
inputs = [x] # like args, but with slices flattened
if not isinstance(idxs, (list, tuple)):
idxs = (idxs,)
for idx in idxs:
try:
ai = asidx(idx)
idx_list.append(len(inputs))
inputs.append(ai)
except TypeError:
if isinstance(idx, slice):
start = None if idx.start is None else asidx(idx.start)
stop = None if idx.stop is None else asidx(idx.stop)
step = None if idx.step is None else asidx(idx.step)
# If we get here, then everything got turned (successfully)
# into a scal.Scalar (with integer dtype) or None
if start:
startpos = len(inputs)
inputs.append(start)
else:
startpos = None
if stop:
stoppos = len(inputs)
inputs.append(stop)
else:
stoppos = None
if step:
steppos = len(inputs)
inputs.append(step)
else:
steppos = None
idx_list.append(slice(startpos, stoppos, steppos))
else:
raise
assert len(idxs) == len(idx_list)
return Subtensor( inputs, idx_list, **kwargs)
def __init__(self, inputs, idx_list, **kwargs):
if len(idx_list) > len(inputs[0].broadcastable):
raise ValueError(Subtensor.e_invalid,
(len(idx_list), len(inputs[0].broadcastable)))
#infer the broadcasting pattern
padded = list(idx_list) \
+ [slice(0,sys.maxint,1)] * (len(inputs[0].broadcastable) - len(idx_list))
broadcastable = [False for p in padded if isinstance(p, slice)]
Op.__init__(self, **kwargs)
self.inputs = inputs
self.outputs = [Tensor(self.inputs[0].dtype, broadcastable)]
self.idx_list = idx_list
def view_map(self):
return {self.out: [self.inputs[0]]}
def perform(self):
x = self.inputs[0].data
cdata = []
for c in self.idx_list:
if isinstance(c, slice):
cdata.append(slice(
None if c.start is None else self.inputs[c.start].data,
None if c.stop is None else self.inputs[c.stop].data,
None if c.step is None else self.inputs[c.step].data))
else:
d = self.inputs[c].data
assert 'int' in str(d.dtype)
cdata.append(d)
if len(cdata) > 1:
cdata = tuple(cdata) #there's a diff between tuple and list here...
else:
cdata = cdata[0]
self.outputs[0].data = x.__getitem__(cdata)
if Subtensor.debug:
print self.inputs[0].data, cdata, self.outputs[0].data
def grad(self, inputs, (gz,)):
return [Subtensor_dx(self.inputs + [gz], self.idx_list).outputs[0]]\
+ [None] * (len(inputs)-1)
def clone_with_new_inputs(self, *new_inputs):
assert len(self.inputs) == len(new_inputs)
return Subtensor(new_inputs, self.idx_list)
class VerticalStack(Op):
"""
Vertically stack two L{Tensor}s.
Stack two L{Tensor}s along the first axis (row wise). These
L{Tensor}s must have the same shape along all dimensions but the
first.
@attention: Because we use vstack as the implementation, if the
inputs have 1-dimension, the output will have 2-dimensions.
"""
def make_node(self, x, y):
x = as_tensor(x)
y = as_tensor(y)
assert x.type.dtype == y.type.dtype
if x.type.broadcastable[1:] != y.type.broadcastable[1:]:
raise NotImplementedError
inputs = [x, y]
bcastable = (False, ) + x.type.broadcastable[1:]
outputs = [tensor(dtype = x.type.dtype,
broadcastable = bcastable)]
return Apply(self, inputs, outputs)
def perform(self, node, (x, y), (out, )):
assert x.ndim == y.ndim
# Make sure every dimension (save the first) is the same
for i in range(x.ndim): assert i == 0 or x.shape[i] == y.shape[i]
out[0] = numpy.vstack([x, y])
def grad(self, (x, y), (gz,)):
"""
@todo: Make VSplit (or this grad implementation) its own L{Op},
that way we can do more sanity-checking::
assert x.ndim == y.ndim
# Make sure every dimension (save the first) is the same
for i in range(x.data.ndim): assert i == 0 or x.data.shape[i] == y.shape[i]
etc...
"""
xs = shape(x)
ys = shape(y)
return gz[:xs[0]], gz[xs[0]:]
vertical_stack = VerticalStack()
def horizontal_stack(x, y):
"""
Horizontally stack two L{Tensor}s.
Stack two L{Tensor}s along the second axis (column wise). These
L{Tensor}s must have the same shape along all dimensions but the
second.
@note: Unlike VerticalStack, we assume that the L{Tensor}s have
two dimensions.
"""
assert x.type.ndim == 2
assert y.type.ndim == 2
return transpose(vertical_stack(x.T, y.T))
######################### #########################
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论