提交 fd21c5cc authored 作者: lamblin's avatar lamblin

Merge pull request #524 from nouiz/sparse

Sparse
...@@ -17,5 +17,7 @@ API ...@@ -17,5 +17,7 @@ API
.. automodule:: theano.sparse.sandbox.sp .. automodule:: theano.sparse.sandbox.sp
:members: :members:
.. automodule:: theano.sparse.sandbox.sp2
:members:
.. automodule:: theano.sparse.sandbox.truedot .. automodule:: theano.sparse.sandbox.truedot
:members: :members:
...@@ -16,12 +16,13 @@ from theano.sandbox.cuda import CudaNdarrayType, cuda_available ...@@ -16,12 +16,13 @@ from theano.sandbox.cuda import CudaNdarrayType, cuda_available
if cuda_available == False: if cuda_available == False:
raise SkipTest('Optional package cuda disabled') raise SkipTest('Optional package cuda disabled')
def test_float32_shared_constructor(): def test_float32_shared_constructor():
npy_row = numpy.zeros((1,10), dtype='float32') npy_row = numpy.zeros((1, 10), dtype='float32')
def eq(a,b): def eq(a, b):
return a==b return a == b
# test that we can create a CudaNdarray # test that we can create a CudaNdarray
assert (f32sc(npy_row).type == CudaNdarrayType((False, False))) assert (f32sc(npy_row).type == CudaNdarrayType((False, False)))
...@@ -40,37 +41,41 @@ def test_float32_shared_constructor(): ...@@ -40,37 +41,41 @@ def test_float32_shared_constructor():
# test that we can make non-matrix shared vars # test that we can make non-matrix shared vars
assert eq( assert eq(
f32sc(numpy.zeros((2,3,4,5), dtype='float32')).type, f32sc(numpy.zeros((2, 3, 4, 5), dtype='float32')).type,
CudaNdarrayType((False,)*4)) CudaNdarrayType((False,) * 4))
def test_givens(): def test_givens():
# Test that you can use a TensorType expression to replace a # Test that you can use a TensorType expression to replace a
# CudaNdarrayType in the givens dictionary. # CudaNdarrayType in the givens dictionary.
# This test case uses code mentionned in #757 # This test case uses code mentionned in #757
data = numpy.float32([1,2,3,4]) data = numpy.float32([1, 2, 3, 4])
x = f32sc(data) x = f32sc(data)
y = x**2 y = x ** 2
f = theano.function([], y, givens={x:x+1}) f = theano.function([], y, givens={x: x + 1})
f()
class T_updates(unittest.TestCase): class T_updates(unittest.TestCase):
# Test that you can use a TensorType expression to update a # Test that you can use a TensorType expression to update a
# CudaNdarrayType in the updates dictionary. # CudaNdarrayType in the updates dictionary.
def test_1(self): def test_1(self):
data = numpy.float32([1,2,3,4]) data = numpy.float32([1, 2, 3, 4])
x = f32sc(data) x = f32sc(data)
y = x**2 y = x ** 2
f = theano.function([], y, updates={x:x+1}) f = theano.function([], y, updates={x: x + 1})
f()
def test_2(self): def test_2(self):
# This test case uses code mentionned in #698 # This test case uses code mentionned in #698
data = numpy.random.rand(10,10).astype('float32') data = numpy.random.rand(10, 10).astype('float32')
output_var = f32sc(name="output", output_var = f32sc(name="output",
value=numpy.zeros((10,10), 'float32')) value=numpy.zeros((10, 10), 'float32'))
x = tensor.fmatrix('x') x = tensor.fmatrix('x')
output_updates = {output_var:x**2} output_updates = {output_var: x ** 2}
output_givens = {x:data} output_givens = {x: data}
output_func = theano.function(inputs=[], outputs=[], output_func = theano.function(inputs=[], outputs=[],
updates=output_updates, givens=output_givens) updates=output_updates, givens=output_givens)
output_func() output_func()
...@@ -78,16 +83,16 @@ class T_updates(unittest.TestCase): ...@@ -78,16 +83,16 @@ class T_updates(unittest.TestCase):
def test_3(self): def test_3(self):
# Test that broadcastable dimensions don't screw up # Test that broadcastable dimensions don't screw up
# update expressions. # update expressions.
data = numpy.random.rand(10,10).astype('float32') data = numpy.random.rand(10, 10).astype('float32')
output_var = f32sc(name="output", output_var = f32sc(name="output", value=data)
value=numpy.zeros((10,10), 'float32'))
# the update_var has type matrix, and the update expression # the update_var has type matrix, and the update expression
# is a broadcasted scalar, and that should be allowed. # is a broadcasted scalar, and that should be allowed.
output_func = theano.function(inputs=[], outputs=[], output_func = theano.function(inputs=[], outputs=[],
updates={output_var:output_var.sum().dimshuffle('x', 'x')}) updates={output_var: output_var.sum().dimshuffle('x', 'x')})
output_func() output_func()
class T_ifelse(unittest.TestCase): class T_ifelse(unittest.TestCase):
def setUp(self): def setUp(self):
utt.seed_rng() utt.seed_rng()
...@@ -111,10 +116,10 @@ class T_ifelse(unittest.TestCase): ...@@ -111,10 +116,10 @@ class T_ifelse(unittest.TestCase):
f = theano.function([cond], out1) f = theano.function([cond], out1)
g = theano.function([cond], out2) g = theano.function([cond], out2)
assert numpy.all(f(0) == data+1) assert numpy.all(f(0) == data + 1)
assert numpy.all(f(1) == data) assert numpy.all(f(1) == data)
assert numpy.all(g(0) == data) assert numpy.all(g(0) == data)
assert numpy.all(g(1) == data+1) assert numpy.all(g(1) == data + 1)
def test_dtype_mismatch(self): def test_dtype_mismatch(self):
data = self.rng.rand(5).astype('float32') data = self.rng.rand(5).astype('float32')
...@@ -135,7 +140,7 @@ class T_ifelse(unittest.TestCase): ...@@ -135,7 +140,7 @@ class T_ifelse(unittest.TestCase):
self.assertRaises(TypeError, ifelse, cond, y, x) self.assertRaises(TypeError, ifelse, cond, y, x)
def test_broadcast_mismatch(self): def test_broadcast_mismatch(self):
data = self.rng.rand(2,3).astype('float32') data = self.rng.rand(2, 3).astype('float32')
x = f32sc(data) x = f32sc(data)
print x.broadcastable print x.broadcastable
y = tensor.frow('y') y = tensor.frow('y')
...@@ -146,7 +151,7 @@ class T_ifelse(unittest.TestCase): ...@@ -146,7 +151,7 @@ class T_ifelse(unittest.TestCase):
self.assertRaises(TypeError, ifelse, cond, y, x) self.assertRaises(TypeError, ifelse, cond, y, x)
def test_sparse_tensor_error(self): def test_sparse_tensor_error(self):
data = self.rng.rand(2,3).astype('float32') data = self.rng.rand(2, 3).astype('float32')
x = f32sc(data) x = f32sc(data)
y = sparse.matrix('csc', dtype='float32', name='y') y = sparse.matrix('csc', dtype='float32', name='y')
z = sparse.matrix('csr', dtype='float32', name='z') z = sparse.matrix('csr', dtype='float32', name='z')
...@@ -160,5 +165,3 @@ class T_ifelse(unittest.TestCase): ...@@ -160,5 +165,3 @@ class T_ifelse(unittest.TestCase):
self.assertRaises((TypeError, ValueError), ifelse, cond, z, x) self.assertRaises((TypeError, ValueError), ifelse, cond, z, x)
self.assertRaises((TypeError, ValueError), ifelse, cond, y, z) self.assertRaises((TypeError, ValueError), ifelse, cond, y, z)
self.assertRaises((TypeError, ValueError), ifelse, cond, z, y) self.assertRaises((TypeError, ValueError), ifelse, cond, z, y)
from theano.sparse.basic import * # To facilitate later merge into sparse module import numpy
from theano import gof, tensor, scalar
from theano.tensor import blas
from theano.sparse.basic import ( from theano.sparse.basic import (
_is_sparse, _is_sparse_variable, _is_dense_variable, as_sparse_variable, SparseType, add_s_s, neg,
_is_sparse, _is_dense, _kmap_eq, _kmap_hash) mul_s_s, mul_s_d,
CSMProperties, CSM, register_specialize,
_is_sparse_variable, CSC, CSR,
csm_data, csm_indices, csm_indptr, csm_shape,
_is_sparse)
class Cast(gof.op.Op): class Cast(gof.op.Op):
...@@ -33,19 +41,21 @@ def local_add_s_s(node): ...@@ -33,19 +41,21 @@ def local_add_s_s(node):
""" """
If two matrices are known to have the same sparsity pattern, If two matrices are known to have the same sparsity pattern,
optimize the addition by only adding their data vector. optimize the addition by only adding their data vector.
Very special case optimization. Activate when for add(x, y), Very special case optimization. Activate when for add(x, y),
y is an expression like sp_ones_like(x) * another_matrix. y is an expression like sp_ones_like(x) * another_matrix.
This is useful for sparse weight updates. This is useful for sparse weight updates.
Work also for add(x, neg(y)) in the same case. Work also for add(x, neg(y)) in the same case.
As of this writting sub is only implemented as x + neg(y) for sparse matrix.
As of this writting sub is only implemented as x + neg(y) for
sparse matrix.
""" """
if node.op == add_s_s: if node.op == add_s_s:
x, y = node.inputs x, y = node.inputs
# In case addition was transformed to subtraction # In case addition was transformed to subtraction
if hasattr(y.owner, 'op') and y.owner.op == neg: if hasattr(y.owner, 'op') and y.owner.op == neg:
y_ = y.owner.inputs[0] y_ = y.owner.inputs[0]
else: else:
...@@ -54,38 +64,46 @@ def local_add_s_s(node): ...@@ -54,38 +64,46 @@ def local_add_s_s(node):
return False return False
if hasattr(y_.owner, 'op') and y_.owner.op not in [mul_s_s, mul_s_d]: if hasattr(y_.owner, 'op') and y_.owner.op not in [mul_s_s, mul_s_d]:
return False return False
def same_pattern(node): def same_pattern(node):
"""Check node has same sparsity as x.""" """Check node has same sparsity as x."""
# In case the sparse matrix is multiplied by a scalar (ex: learning rate) # In case the sparse matrix is multiplied by a scalar (ex:
# learning rate)
if hasattr(node.owner, 'op') and node.owner.op == mul_scalar: if hasattr(node.owner, 'op') and node.owner.op == mul_scalar:
node = node.owner.inputs[1] node = node.owner.inputs[1]
# Check node creates a matrix # Check node creates a matrix
if not hasattr(node.owner, 'op') or not isinstance(node.owner.op, CSM): if not hasattr(node.owner, 'op') or not isinstance(node.owner.op,
return False CSM):
return False
# Check matrix is creates from CSMProperties # Check matrix is creates from CSMProperties
if filter(lambda i: not hasattr(i.owner, 'op') or not isinstance(i.owner.op, CSMProperties), node.owner.inputs[1:]): if filter(lambda i: not hasattr(i.owner, 'op') or
return False not isinstance(i.owner.op, CSMProperties),
node.owner.inputs[1:]):
return False
# Verify indices, indptr and shape are the same as x # Verify indices, indptr and shape are the same as x
if filter(lambda i: i.owner.inputs[0] != x, node.owner.inputs[1:]): if filter(lambda i: i.owner.inputs[0] != x, node.owner.inputs[1:]):
return False return False
return True return True
if filter(same_pattern, y_.owner.inputs): if filter(same_pattern, y_.owner.inputs):
return [add_s_s_data(x, y)] return [add_s_s_data(x, y)]
return False return False
register_specialize(local_add_s_s) register_specialize(local_add_s_s)
class AddSSData(gof.op.Op): class AddSSData(gof.op.Op):
'''Add two sparse matrices assuming they have the same sparsity pattern. ''' '''Add two sparse matrices assuming they have the same sparsity
pattern. '''
def __eq__(self, other): def __eq__(self, other):
return (type(self) == type(other)) return (type(self) == type(other))
def __hash__(self): def __hash__(self):
return hash(type(self)) return hash(type(self))
def make_node(self, x, y): def make_node(self, x, y):
x, y = map(as_sparse_variable, [x, y]) x, y = map(as_sparse_variable, [x, y])
if x.type.dtype != y.type.dtype: if x.type.dtype != y.type.dtype:
...@@ -94,46 +112,50 @@ class AddSSData(gof.op.Op): ...@@ -94,46 +112,50 @@ class AddSSData(gof.op.Op):
raise NotImplementedError() raise NotImplementedError()
return gof.Apply(self, return gof.Apply(self,
[x, y], [x, y],
[SparseType(dtype = x.type.dtype, [SparseType(dtype=x.type.dtype,
format = x.type.format).make_variable()]) format=x.type.format).make_variable()])
def perform(self, node, (x, y), (out, )):
def perform(self, node, (x, y), (out, )):
assert _is_sparse(x) and _is_sparse(y) assert _is_sparse(x) and _is_sparse(y)
assert x.shape == y.shape assert x.shape == y.shape
out[0] = x.copy() out[0] = x.copy()
out[0].data += y.data out[0].data += y.data
add_s_s_data = AddSSData() add_s_s_data = AddSSData()
# register a specialization to replace MulSD -> MulSDCSX # register a specialization to replace MulSD -> MulSDCSX
@gof.local_optimizer([mul_s_d]) @gof.local_optimizer([mul_s_d])
def local_mul_s_d(node): def local_mul_s_d(node):
if node.op == mul_s_d: if node.op == mul_s_d:
x, y = node.inputs x, y = node.inputs
x_is_sparse_variable = _is_sparse_variable(x) x_is_sparse_variable = _is_sparse_variable(x)
y_is_sparse_variable = _is_sparse_variable(y) # y_is_sparse_variable = _is_sparse_variable(y)
if x_is_sparse_variable: if x_is_sparse_variable:
svar = x svar = x
dvar = y dvar = y
else: else:
svar = y svar = y
dvar = x dvar = x
if dvar.type.ndim != 2: if dvar.type.ndim != 2:
return False return False
if svar.type.format == 'csc': if svar.type.format == 'csc':
CSx = CSC CSx = CSC
mul_s_d_csx = mul_s_d_csc mul_s_d_csx = mul_s_d_csc
elif svar.type.format == 'csr': elif svar.type.format == 'csr':
CSx = CSR CSx = CSR
mul_s_d_csx = mul_s_d_csr mul_s_d_csx = mul_s_d_csr
else: else:
raise NotImplemented() raise NotImplemented()
c_data = mul_s_d_csx(csm_data(svar), csm_indices(svar), csm_indptr(svar), dvar) c_data = mul_s_d_csx(csm_data(svar), csm_indices(svar),
csm_indptr(svar), dvar)
return [CSx(c_data, csm_indices(svar), csm_indptr(svar), csm_shape(svar))]
return [CSx(c_data, csm_indices(svar), csm_indptr(svar),
csm_shape(svar))]
return False return False
register_specialize(local_mul_s_d) register_specialize(local_mul_s_d)
...@@ -141,15 +163,19 @@ register_specialize(local_mul_s_d) ...@@ -141,15 +163,19 @@ register_specialize(local_mul_s_d)
class MulSDCSC(gof.Op): class MulSDCSC(gof.Op):
def __eq__(self, other): def __eq__(self, other):
return (type(self) == type(other)) return (type(self) == type(other))
def __hash__(self): def __hash__(self):
return hash(type(self)) return hash(type(self))
def make_node(self, a_data, a_indices, a_indptr, b): def make_node(self, a_data, a_indices, a_indptr, b):
assert b.type.ndim == 2 assert b.type.ndim == 2
return gof.Apply(self, [a_data, a_indices, a_indptr, b], return gof.Apply(self, [a_data, a_indices, a_indptr, b],
[tensor.tensor(b.dtype, (False,))]) [tensor.tensor(b.dtype, (False,))])
#def perform(self, node, (a_data, a_indices, a_indptr, b), (out,)): #def perform(self, node, (a_data, a_indices, a_indptr, b), (out,)):
# return NotImplementedError() # return NotImplementedError()
def c_code(self, node, name, (_data, _indices, _indptr, _b,), (_zout, ), sub): def c_code(self, node, name, (_data, _indices, _indptr, _b,),
(_zout, ), sub):
if node.inputs[0].type.dtype in ('complex64', 'complex128'): if node.inputs[0].type.dtype in ('complex64', 'complex128'):
raise NotImplementedError('Complex types are not supported for a') raise NotImplementedError('Complex types are not supported for a')
...@@ -209,22 +235,26 @@ class MulSDCSC(gof.Op): ...@@ -209,22 +235,26 @@ class MulSDCSC(gof.Op):
} }
} }
"""% dict(locals(), **sub) """ % dict(locals(), **sub)
mul_s_d_csc = MulSDCSC() mul_s_d_csc = MulSDCSC()
class MulSDCSR(gof.Op): class MulSDCSR(gof.Op):
def __eq__(self, other): def __eq__(self, other):
return (type(self) == type(other)) return (type(self) == type(other))
def __hash__(self): def __hash__(self):
return hash(type(self)) return hash(type(self))
def make_node(self, a_data, a_indices, a_indptr, b): def make_node(self, a_data, a_indices, a_indptr, b):
assert b.type.ndim == 2 assert b.type.ndim == 2
return gof.Apply(self, [a_data, a_indices, a_indptr, b], return gof.Apply(self, [a_data, a_indices, a_indptr, b],
[tensor.tensor(b.dtype, (False,))]) [tensor.tensor(b.dtype, (False,))])
#def perform(self, node, (a_data, a_indices, a_indptr, b), (out,)): #def perform(self, node, (a_data, a_indices, a_indptr, b), (out,)):
# return NotImplemented() # return NotImplemented()
def c_code(self, node, name, (_data, _indices, _indptr, _b,), (_zout, ), sub): def c_code(self, node, name, (_data, _indices, _indptr, _b,),
(_zout, ), sub):
if node.inputs[0].type.dtype in ('complex64', 'complex128'): if node.inputs[0].type.dtype in ('complex64', 'complex128'):
raise NotImplementedError('Complex types are not supported for a') raise NotImplementedError('Complex types are not supported for a')
...@@ -284,9 +314,10 @@ class MulSDCSR(gof.Op): ...@@ -284,9 +314,10 @@ class MulSDCSR(gof.Op):
} }
} }
"""% dict(locals(), **sub) """ % dict(locals(), **sub)
mul_s_d_csr = MulSDCSR() mul_s_d_csr = MulSDCSR()
class Poisson(gof.op.Op): class Poisson(gof.op.Op):
def __eq__(self, other): def __eq__(self, other):
return (type(self) == type(other)) return (type(self) == type(other))
...@@ -306,6 +337,7 @@ class Poisson(gof.op.Op): ...@@ -306,6 +337,7 @@ class Poisson(gof.op.Op):
out[0].eliminate_zeros() out[0].eliminate_zeros()
poisson = Poisson() poisson = Poisson()
class Multinomial(gof.op.Op): class Multinomial(gof.op.Op):
def __eq__(self, other): def __eq__(self, other):
return (type(self) == type(other)) return (type(self) == type(other))
...@@ -713,7 +745,6 @@ class SamplingDotCsr(gof.Op): ...@@ -713,7 +745,6 @@ class SamplingDotCsr(gof.Op):
return blas.blas_header_text() return blas.blas_header_text()
def c_libraries(self): def c_libraries(self):
import pdb; pdb.set_trace()
return blas.ldflags() return blas.ldflags()
def c_compile_args(self): def c_compile_args(self):
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论