提交 5af58c40 authored 作者: turian@grenat.iro.umontreal.ca's avatar turian@grenat.iro.umontreal.ca

merge

...@@ -2,6 +2,7 @@ from sparse import * ...@@ -2,6 +2,7 @@ from sparse import *
import unittest import unittest
import compile import compile
import gradient
class T_transpose(unittest.TestCase): class T_transpose(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -35,12 +36,13 @@ class T_Add(unittest.TestCase): ...@@ -35,12 +36,13 @@ class T_Add(unittest.TestCase):
def test0(self): def test0(self):
sp_a = sparse.csc_matrix(sparse.speye(5,3)) sp_a = sparse.csc_matrix(sparse.speye(5,3))
a = assparse(sp_a) a = assparse(sp_a)
self.failUnless(a.data is sp_a)
sp_b = sparse.csc_matrix(sparse.speye(5,3)) sp_b = sparse.csc_matrix(sparse.speye(5,3))
b = assparse(sp_b) b = assparse(sp_b)
self.failUnless(b.data is sp_b)
self.failUnless(a.data is sp_a) apb = add(a, b)
apb = add_s_s(a, b)
self.failUnless(apb.dtype == a.dtype, apb.dtype) self.failUnless(apb.dtype == a.dtype, apb.dtype)
self.failUnless(apb.format == a.format, apb.format) self.failUnless(apb.format == a.format, apb.format)
...@@ -55,7 +57,7 @@ class T_conversion(unittest.TestCase): ...@@ -55,7 +57,7 @@ class T_conversion(unittest.TestCase):
def test0(self): def test0(self):
a = tensor.astensor(numpy.random.rand(5)) a = tensor.astensor(numpy.random.rand(5))
s = sparse_from_dense(a,'csc') s = sparse_from_dense(a, 'csc')
val = compile.eval_outputs([s]) val = compile.eval_outputs([s])
self.failUnless(str(val.dtype)=='float64') self.failUnless(str(val.dtype)=='float64')
self.failUnless(val.format == 'csc') self.failUnless(val.format == 'csc')
...@@ -79,6 +81,7 @@ class T_conversion(unittest.TestCase): ...@@ -79,6 +81,7 @@ class T_conversion(unittest.TestCase):
class _testCase_dot(unittest.TestCase): class _testCase_dot(unittest.TestCase):
""" Types of sparse matrices to use for testing """ """ Types of sparse matrices to use for testing """
mtypes = [sparse.csc_matrix, sparse.csr_matrix] mtypes = [sparse.csc_matrix, sparse.csr_matrix]
mtypes_str = ["csc", "csr"]
#mtypes = [sparse.csc_matrix, sparse.csr_matrix, sparse.dok_matrix, sparse.lil_matrix, sparse.coo_matrix] #mtypes = [sparse.csc_matrix, sparse.csr_matrix, sparse.dok_matrix, sparse.lil_matrix, sparse.coo_matrix]
def setUp(self): def setUp(self):
...@@ -126,7 +129,6 @@ class _testCase_dot(unittest.TestCase): ...@@ -126,7 +129,6 @@ class _testCase_dot(unittest.TestCase):
zop = transpose(dot(y, x)) zop = transpose(dot(y, x))
z = compile.eval_outputs([zop]) z = compile.eval_outputs([zop])
self.failUnless(z.shape == (500,2)) self.failUnless(z.shape == (500,2))
print mtype, type(z)
# self.failUnless(type(z) is mtype) # self.failUnless(type(z) is mtype)
w = mtype((500,2)) w = mtype((500,2))
...@@ -147,51 +149,30 @@ class _testCase_dot(unittest.TestCase): ...@@ -147,51 +149,30 @@ class _testCase_dot(unittest.TestCase):
w = w.todense() w = w.todense()
self.failUnless((z == w).all() == True) self.failUnless((z == w).all() == True)
# def test_basic1(self):
# """dot: sparse left"""
# a = numpy.asarray([[1, 0, 3, 0, 5], [0, 0, -2, 0, 0]],
# dtype='float64')
# b = numpy.random.rand(5, 3)
# for mtype in [sparse.csr_matrix, sparse.csc_matrix, sparse.dok_matrix,
# sparse.lil_matrix]:#, sparse.coo_matrix]:
# #print type(a), mtype
# m = mtype(a)
# ab = m.dot(b)
# try:
# z = dot(assparse(m), gof.Result(data=b))
# self.failUnless(z.data.shape == ab.shape)
# self.failUnless(type(z.data) == type(ab))
# except Exception, e:
# print 'cccc', mtype, e, str(e)
# raise
def test_missing(self): def test_missing(self):
raise NotImplementedError('tests commented out') raise NotImplementedError('tests commented out')
# def test_basic2(self): def test_graph_bprop0(self):
# """dot: sparse right"""
# a = numpy.random.rand(2, 5)
# b = numpy.asarray([[1, 0, 3, 0, 5], [0, 0, -2, 0, 0]],
# dtype='float64').transpose()
#
# for mtype in [sparse.csr_matrix, sparse.csc_matrix, sparse.dok_matrix,
# sparse.lil_matrix]:#, sparse.coo_matrix]:
# m = mtype(b)
# ab = m.transpose().dot(a.transpose()).transpose()
# z = dot(gof.Result(data=a),assparse(mtype(b)))
# self.failUnless(z.data.shape == ab.shape)
# self.failUnless(type(z.data) == type(ab))
#
# def test_graph_bprop0(self):
# x = tensor.astensor(numpy.random.rand(10,2)) # x = tensor.astensor(numpy.random.rand(10,2))
# w = assparse(sparse.csr_matrix( # w = assparse(sparse.csr_matrix(
# numpy.asarray([[1, 0, 3, 0, 5], [0, 0, -2, 0,0]],dtype='float64') # numpy.asarray([[1, 0, 3, 0, 5], [0, 0, -2, 0,0]],dtype='float64')
# )) # ))
# for mtype in self.mtypes_str:
# x = tensor.astensor([[1., 2], [3, 4], [2, 1]])
# w = assparse(mtype((500,3)))
# w.data[(10, 1)] = 1
# w.data[(20, 2)] = 2
x = tensor.Tensor('float64', broadcastable=[False,False], name='x')
w = SparseResult('float64', mtype)
xw = dense_from_sparse(dot(x, w))
y = dense_from_sparse(dot(xw, w.T))
diff = x-y
loss = tensor.sum(tensor.sqr(diff))
gw = gradient.grad(loss, w)
trainfn = compile.Function([x, w], [y, loss, gw])
# for epoch in xrange(50): # for epoch in xrange(50):
# xw = dense_from_sparse(dot(x, w))
# y = dense_from_sparse(dot(xw, transpose(w)))
# loss = core.sum(core.sqr(x-y))
# gy = y-x # gy = y-x
# g = grad.Grad({y:gy}) # g = grad.Grad({y:gy})
# g.bprop() # g.bprop()
...@@ -199,9 +180,10 @@ class _testCase_dot(unittest.TestCase): ...@@ -199,9 +180,10 @@ class _testCase_dot(unittest.TestCase):
# g(w).data[1,0] = 0 # g(w).data[1,0] = 0
# g(w).data[1,4] = 0 # g(w).data[1,4] = 0
# w.data = -lr * g(w).data + w.data # w.data = -lr * g(w).data + w.data
# # print loss.data
# self.failUnless('3.08560636025' == str(loss.data))
# self.failUnless('3.08560636025' == str(loss.data))
# def test_graph_bprop1(self): # def test_graph_bprop1(self):
# x = tensor.astensor(numpy.random.rand(10,2)) # x = tensor.astensor(numpy.random.rand(10,2))
# w = assparse(sparse.csr_matrix( # w = assparse(sparse.csr_matrix(
......
...@@ -14,9 +14,13 @@ class BaseTensor(Result): ...@@ -14,9 +14,13 @@ class BaseTensor(Result):
L{Result} to store L{numpy.ndarray} or equivalent via .data L{Result} to store L{numpy.ndarray} or equivalent via .data
@type _dtype: numpy dtype string such as 'int64' or 'float64' (among others) @type _dtype: numpy dtype string such as 'int64' or 'float64' (among others)
@type _broadcastable: - tuple of ints in (0,1) @type _broadcastable: tuple or list or array of boolean values, whose length
@ivar _broadcastable: which dimensions of this tensor are guaranteed is the number of dimensions of the contained L{ndarray}.
to be 1, and up for broadcasting. @ivar _broadcastable: Each element of the broadcastable vector tells us
something about the corresponding dimension:
- False means the dimension can be anything.
- True means the dimension must be 1. Also, this dimension will be considered
for L{broadcasting}, as described and implemented in Numpy.
Properties: Properties:
dtype - read-only access to _dtype, which should not be changed dtype - read-only access to _dtype, which should not be changed
...@@ -24,12 +28,13 @@ class BaseTensor(Result): ...@@ -24,12 +28,13 @@ class BaseTensor(Result):
This class does not implement python operators and has no dependencies This class does not implement python operators and has no dependencies
on the L{Op}s that use it. on the L{Op}s that use it.
@todo At some point we should document a glossary, such as terms like
broadcasting and shape.
""" """
def __init__(self, dtype, broadcastable, name=None): def __init__(self, dtype, broadcastable, name=None):
"""Initialize a L{Tensor} """Initialize a L{BaseTensor}
@todo: Initialize a L{Tensor} or a L{BaseTensor}? -jpt
@note: This does not actually allocate any data. @note: This does not actually allocate any data.
""" """
...@@ -103,7 +108,7 @@ class BaseTensor(Result): ...@@ -103,7 +108,7 @@ class BaseTensor(Result):
# #
def desc(self): def desc(self):
""" """
Returns a hashable description of this BaseTensor. Returns a hashable description of this L{BaseTensor}.
""" """
if self.data is not None: if self.data is not None:
return (BaseTensor, self.dtype, self.broadcastable, self.data.data[:]) return (BaseTensor, self.dtype, self.broadcastable, self.data.data[:])
......
...@@ -120,6 +120,10 @@ def grad_sources_inputs(sources, graph_inputs): ...@@ -120,6 +120,10 @@ def grad_sources_inputs(sources, graph_inputs):
def grad(cost, param, g_cost=1.0): def grad(cost, param, g_cost=1.0):
""" """
@type cost: L{Result}
@type param: L{Result} or list of L{Result}s.
@rtype: L{Result} or list of L{Result}s (depending upon I{param})
@return: symbolic expression of gradient of I{cost} wrt I{param}. @return: symbolic expression of gradient of I{cost} wrt I{param}.
If I{param} is a list, then return a list containing the gradient of I{cost} wrt If I{param} is a list, then return a list containing the gradient of I{cost} wrt
each element of the list. each element of the list.
......
...@@ -11,7 +11,46 @@ import numpy ...@@ -11,7 +11,46 @@ import numpy
from scipy import sparse from scipy import sparse
import gof.op, gof.result import gof.op, gof.result
import tensor import tensor, base_tensor
## Type checking
def _is_sparse_result(x):
"""
@rtype: boolean
@return: True iff x is a L{SparseResult} (and not a L{base_tensor.BaseTensor})
"""
if not isinstance(x, SparseResult) and not isinstance(x, base_tensor.BaseTensor):
raise NotImplementedError("_is_sparse should only be called on sparse.SparseResult or base_tensor.BaseTensor, 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{base_tensor.BaseTensor})
"""
if not isinstance(x, SparseResult) and not isinstance(x, base_tensor.BaseTensor):
raise NotImplementedError("_is_sparse should only be called on sparse.SparseResult or base_tensor.BaseTensor, not,", x)
return isinstance(x, SparseResult)
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 # Wrapper type
...@@ -26,12 +65,13 @@ def assparse(sp, **kwargs): ...@@ -26,12 +65,13 @@ def assparse(sp, **kwargs):
@todo Verify that sp is sufficiently sparse, and raise a warning if it is not @todo Verify that sp is sufficiently sparse, and raise a warning if it is not
""" """
if isinstance(sp, SparseResult): if isinstance(sp, SparseResult):
return sp rval = sp
else: else:
# @todo Verify that sp is sufficiently sparse, and raise a # @todo Verify that sp is sufficiently sparse, and raise a
# warning if it is not # warning if it is not
rval = SparseResult(str(sp.dtype), sp.format, **kwargs) rval = SparseResult(str(sp.dtype), sp.format, **kwargs)
rval.data = sp rval.data = sp
assert _is_sparse_result(rval)
return rval return rval
class SparseResult(gof.result.Result): class SparseResult(gof.result.Result):
...@@ -41,6 +81,10 @@ class SparseResult(gof.result.Result): ...@@ -41,6 +81,10 @@ class SparseResult(gof.result.Result):
Properties: Properties:
- T - read-only: return a transpose of self - T - read-only: return a transpose of self
Methods:
@note As far as I can tell, L{scipy.sparse} objects must be matrices, i.e. have dimension 2.
""" """
format_cls = { format_cls = {
'csr' : sparse.csr_matrix, 'csr' : sparse.csr_matrix,
...@@ -96,7 +140,6 @@ class SparseResult(gof.result.Result): ...@@ -96,7 +140,6 @@ class SparseResult(gof.result.Result):
def __add__(left, right): return add(left, right) def __add__(left, right): return add(left, right)
def __radd__(right, left): return add(left, right) def __radd__(right, left): return add(left, right)
# #
# Conversion # Conversion
# #
...@@ -108,9 +151,11 @@ class DenseFromSparse(gof.op.Op): ...@@ -108,9 +151,11 @@ class DenseFromSparse(gof.op.Op):
self.inputs = [assparse(x)] self.inputs = [assparse(x)]
self.outputs = [tensor.Tensor(x.dtype,[0,0])] self.outputs = [tensor.Tensor(x.dtype,[0,0])]
def impl(self, x): def impl(self, x):
assert _is_sparse(x)
return numpy.asarray(x.todense()) return numpy.asarray(x.todense())
def grad(self, x, gz): def grad(self, (x,), (gz,)):
return sparse_from_dense(gz, x.format) assert _is_sparse_result(x) and _is_dense_result(gz)
return sparse_from_dense(gz, x.format),
dense_from_sparse = gof.op.constructor(DenseFromSparse) dense_from_sparse = gof.op.constructor(DenseFromSparse)
class SparseFromDense(gof.op.Op): class SparseFromDense(gof.op.Op):
...@@ -125,9 +170,11 @@ class SparseFromDense(gof.op.Op): ...@@ -125,9 +170,11 @@ class SparseFromDense(gof.op.Op):
def impl(self, x, fmt): def impl(self, x, fmt):
# this would actually happen anyway when we try to assign to # this would actually happen anyway when we try to assign to
# self.outputs[0].data, but that seems hackish -JB # self.outputs[0].data, but that seems hackish -JB
assert _is_dense(x)
return SparseResult.format_cls[fmt](x) return SparseResult.format_cls[fmt](x)
def grad(self, (x, fmt), gz): def grad(self, (x, fmt), (gz,)):
return dense_from_sparse(gz) assert _is_dense_result(x) and _is_sparse_result(gz)
return dense_from_sparse(gz), None
sparse_from_dense = gof.op.constructor(SparseFromDense) sparse_from_dense = gof.op.constructor(SparseFromDense)
# Linear Algebra # Linear Algebra
...@@ -142,12 +189,15 @@ class Transpose(gof.op.Op): ...@@ -142,12 +189,15 @@ class Transpose(gof.op.Op):
self.inputs = [x] self.inputs = [x]
self.outputs = [SparseResult(x.dtype, Transpose.format_map[x.format])] self.outputs = [SparseResult(x.dtype, Transpose.format_map[x.format])]
def impl(self, x): def impl(self, x):
assert _is_sparse(x)
return x.transpose() return x.transpose()
def grad(self, x, gz): def grad(self, (x,), (gz,)):
return transpose(gz) assert _is_sparse_result(x) and _is_sparse_result(gz)
return transpose(gz),
transpose = gof.op.constructor(Transpose) transpose = gof.op.constructor(Transpose)
class AddSS(gof.op.Op): #add two sparse matrices class AddSS(gof.op.Op):
''' Add two sparse matrices '''
def __init__(self, x, y, **kwargs): def __init__(self, x, y, **kwargs):
gof.op.Op.__init__(self, **kwargs) gof.op.Op.__init__(self, **kwargs)
x, y = [assparse(x), assparse(y)] x, y = [assparse(x), assparse(y)]
...@@ -158,10 +208,49 @@ class AddSS(gof.op.Op): #add two sparse matrices ...@@ -158,10 +208,49 @@ class AddSS(gof.op.Op): #add two sparse matrices
raise NotImplementedError() raise NotImplementedError()
self.outputs = [SparseResult(x.dtype, x.format)] self.outputs = [SparseResult(x.dtype, x.format)]
def impl(self, x,y): def impl(self, x,y):
assert _is_sparse(x) and _is_sparse(y)
return x + y return x + y
def grad(self, (x, y), gz): def grad(self, (x, y), (gz,)):
assert _is_sparse_result(x) and _is_sparse_result(y)
assert _is_sparse_result(gz)
return gz, gz return gz, gz
add_s_s = gof.op.constructor(AddSS) 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): class Dot(gof.op.Op):
""" """
...@@ -173,6 +262,8 @@ class Dot(gof.op.Op): ...@@ -173,6 +262,8 @@ class Dot(gof.op.Op):
when L{Dot} is in the middle of a larger graph, because the types 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 of gy will match that of y. This conversion might be inefficient if
the gradients are graph outputs though, hence this mask. 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): def __init__(self, x, y, grad_preserves_dense=True):
""" """
...@@ -181,6 +272,7 @@ class Dot(gof.op.Op): ...@@ -181,6 +272,7 @@ class Dot(gof.op.Op):
if x.dtype != y.dtype: if x.dtype != y.dtype:
raise NotImplementedError() raise NotImplementedError()
assert _is_sparse_result(x)
# These are the conversions performed by scipy.sparse.dot # These are the conversions performed by scipy.sparse.dot
if x.format == "csc" or x.format == "coo": if x.format == "csc" or x.format == "coo":
myformat = "csc" myformat = "csc"
...@@ -199,9 +291,10 @@ class Dot(gof.op.Op): ...@@ -199,9 +291,10 @@ class Dot(gof.op.Op):
""" """
self.outputs[0].data = self.inputs[0].data.dot(self.inputs[1].data) self.outputs[0].data = self.inputs[0].data.dot(self.inputs[1].data)
def grad(self, (x, y), (gz,)): def grad(self, (x, y), (gz,)):
assert _is_sparse_result(gz)
rval = [dot(gz, y.T), dot(x.T, gz)] rval = [dot(gz, y.T), dot(x.T, gz)]
assert isinstance(self.inputs[0], SparseResult) assert _is_sparse_result(x)
if not isinstance(self.inputs[1], SparseResult): if _is_dense_result(y):
if self.grad_preserves_dense: if self.grad_preserves_dense:
rval[1] = dense_from_sparse(rval[1]) rval[1] = dense_from_sparse(rval[1])
return rval return rval
...@@ -217,12 +310,12 @@ def dot(x, y, grad_preserves_dense=True): ...@@ -217,12 +310,12 @@ def dot(x, y, grad_preserves_dense=True):
if hasattr(x, 'getnnz'): x = assparse(x) if hasattr(x, 'getnnz'): x = assparse(x)
if hasattr(y, 'getnnz'): y = assparse(y) if hasattr(y, 'getnnz'): y = assparse(y)
x_is_sparse = isinstance(x, SparseResult) x_is_sparse_result = _is_sparse_result(x)
y_is_sparse = isinstance(y, SparseResult) y_is_sparse_result = _is_sparse_result(y)
if not x_is_sparse and not y_is_sparse: if not x_is_sparse_result and not y_is_sparse_result:
raise TypeError() raise TypeError()
if x_is_sparse: if x_is_sparse_result:
return Dot(x,y,grad_preserves_dense).outputs[0] return Dot(x,y,grad_preserves_dense).outputs[0]
else: else:
assert y_is_sparse assert y_is_sparse_result
return transpose(Dot(y.T, x.T, grad_preserves_dense).outputs[0]) return transpose(Dot(y.T, x.T, grad_preserves_dense).outputs[0])
...@@ -84,7 +84,7 @@ def astensor(data, broadcastable=None, name=None): ...@@ -84,7 +84,7 @@ def astensor(data, broadcastable=None, name=None):
raise ValueError("Cannot rename an existing Tensor.") raise ValueError("Cannot rename an existing Tensor.")
return data return data
elif isinstance(data, Result): elif isinstance(data, Result):
raise TypeError("Cannot make a Tensor out of a non-Tensor result.") raise TypeError("Cannot make a Tensor out of a non-Tensor result:", data)
if data is None and broadcastable is None: if data is None and broadcastable is None:
raise TypeError("Cannot make a Tensor out of None.") raise TypeError("Cannot make a Tensor out of None.")
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论