提交 30a7d840 authored 作者: Frédéric Bastien's avatar Frédéric Bastien

Merge pull request #4028 from shabanian/kmap

Kmap
...@@ -93,20 +93,6 @@ def _is_dense(x): ...@@ -93,20 +93,6 @@ def _is_dense(x):
return isinstance(x, numpy.ndarray) return isinstance(x, numpy.ndarray)
def _kmap_eq(a, b):
if a is None and b is None:
return True
if a is None or b is None:
return False
return numpy.all(a == b)
def _kmap_hash(a):
if a is None:
return 12345
return hash(numpy.str(a))
# Wrapper type # Wrapper type
def as_sparse_variable(x, name=None): def as_sparse_variable(x, name=None):
""" """
...@@ -517,9 +503,9 @@ class CSMProperties(gof.Op): ...@@ -517,9 +503,9 @@ class CSMProperties(gof.Op):
# we don't return a view of the shape, we create a new ndarray from the # we don't return a view of the shape, we create a new ndarray from the
# shape tuple. # shape tuple.
__props__ = ()
view_map = {0: [0], 1: [0], 2: [0]} view_map = {0: [0], 1: [0], 2: [0]}
kmap = None
""" """
Indexing to speficied what part of the data parameter Indexing to speficied what part of the data parameter
should be use to construct the sparse matrix. should be use to construct the sparse matrix.
...@@ -527,18 +513,8 @@ class CSMProperties(gof.Op): ...@@ -527,18 +513,8 @@ class CSMProperties(gof.Op):
""" """
def __init__(self, kmap=None): def __init__(self, kmap=None):
self.kmap = kmap if kmap is not None:
raise Exception("Do not use kmap, it is removed")
def __eq__(self, other):
return type(self) == type(other) and _kmap_eq(self.kmap, other.kmap)
def __hash__(self):
return 8234 ^ hash(type(self)) ^ _kmap_hash(self.kmap)
def __str__(self):
return "%s{%s}" % (
self.__class__.__name__,
self.kmap)
def make_node(self, csm): def make_node(self, csm):
csm = as_sparse_variable(csm) csm = as_sparse_variable(csm)
...@@ -551,14 +527,10 @@ class CSMProperties(gof.Op): ...@@ -551,14 +527,10 @@ class CSMProperties(gof.Op):
def perform(self, node, inputs, out): def perform(self, node, inputs, out):
(csm,) = inputs (csm,) = inputs
if self.kmap is None:
out[0][0] = csm.data out[0][0] = csm.data
else:
out[0][0] = csm.data[self.kmap]
if str(csm.data.dtype) == 'int32': if str(csm.data.dtype) == 'int32':
out[0][0] = theano._asarray(out[0][0], dtype='int32') out[0][0] = theano._asarray(out[0][0], dtype='int32')
# backport # backport
# out[0][0] = csm.data if self.kmap is None else csm.data[self.kmap]
out[1][0] = theano._asarray(csm.indices, dtype='int32') out[1][0] = theano._asarray(csm.indices, dtype='int32')
out[2][0] = theano._asarray(csm.indptr, dtype='int32') out[2][0] = theano._asarray(csm.indptr, dtype='int32')
out[3][0] = theano._asarray(csm.shape, dtype='int32') out[3][0] = theano._asarray(csm.shape, dtype='int32')
...@@ -638,14 +610,12 @@ def csm_shape(csm): ...@@ -638,14 +610,12 @@ def csm_shape(csm):
class CSM(gof.Op): class CSM(gof.Op):
# See doc in instance of this Op or function after this class definition. # See doc in instance of this Op or function after this class definition.
kmap = None
""" """
Indexing to speficied what part of the data parameter Indexing to speficied what part of the data parameter
should be used to construct the sparse matrix. should be used to construct the sparse matrix.
""" """
__props__ = ('format',)
_hashval = None
""" """
Pre-computed hash value, defined by __init__. Pre-computed hash value, defined by __init__.
...@@ -655,33 +625,12 @@ class CSM(gof.Op): ...@@ -655,33 +625,12 @@ class CSM(gof.Op):
if format not in ('csr', 'csc'): if format not in ('csr', 'csc'):
raise ValueError("format must be one of: 'csr', 'csc'", format) raise ValueError("format must be one of: 'csr', 'csc'", format)
self.format = format self.format = format
if kmap is not None:
# for efficiency, if remap does nothing, then do not apply it raise Exception("Do not use kmap, it is removed")
if kmap is not None and all(kmap == numpy.arange(numpy.size(kmap))):
kmap = None
self.kmap = kmap
if not isinstance(self.kmap, numpy.ndarray):
# should view the other inputs too, but viewing multiple # should view the other inputs too, but viewing multiple
# inputs is not currently supported by the destroyhandler # inputs is not currently supported by the destroyhandler
self.view_map = {0: [0]} self.view_map = {0: [0]}
self._hashval = (hash(type(self)) ^ hash(self.format) ^
_kmap_hash(self.kmap))
def __eq__(self, other):
return (type(other) is CSM and other.format == self.format and
_kmap_eq(self.kmap, other.kmap))
def __hash__(self):
return self._hashval
def __str__(self):
if self.kmap is not None:
return "%s{%s}" % (self.__class__.__name__, str(self.kmap))
return self.__class__.__name__
def make_node(self, data, indices, indptr, shape): def make_node(self, data, indices, indptr, shape):
data = tensor.as_tensor_variable(data) data = tensor.as_tensor_variable(data)
...@@ -726,18 +675,14 @@ class CSM(gof.Op): ...@@ -726,18 +675,14 @@ class CSM(gof.Op):
# for efficiency, if remap does nothing, then do not apply it # for efficiency, if remap does nothing, then do not apply it
(data, indices, indptr, shape) = inputs (data, indices, indptr, shape) = inputs
(out,) = outputs (out,) = outputs
if self.kmap is not None:
data = data[self.kmap]
if len(shape) != 2: if len(shape) != 2:
raise ValueError('Shape should be an array of length 2') raise ValueError('Shape should be an array of length 2')
if (data.shape != indices.shape and numpy.size(data) != if data.shape != indices.shape:
numpy.size(self.kmap)):
errmsg = ('Data (shape ' + repr(data.shape) + errmsg = ('Data (shape ' + repr(data.shape) +
' must have the same number of elements ' + ' must have the same number of elements ' +
'as indices (shape' + repr(indices.shape) + 'as indices (shape' + repr(indices.shape) +
') or elements as kmap (' + ')')
repr(numpy.size(self.kmap)) + ')')
raise ValueError(errmsg) raise ValueError(errmsg)
if self.format == 'csc': if self.format == 'csc':
out[0] = scipy.sparse.csc_matrix((data, indices.copy(), out[0] = scipy.sparse.csc_matrix((data, indices.copy(),
...@@ -757,17 +702,13 @@ class CSM(gof.Op): ...@@ -757,17 +702,13 @@ class CSM(gof.Op):
(g_out,) = gout (g_out,) = gout
g_data, g_indices, g_indptr, g_shape = csm_properties(g_out) g_data, g_indices, g_indptr, g_shape = csm_properties(g_out)
# unpack the data vector and wrap it as a 1d TensorType # unpack the data vector and wrap it as a 1d TensorType
g_data = csm_grad(self.kmap)(x_data, x_indices, x_indptr, x_shape, g_data = csm_grad()(x_data, x_indices, x_indptr, x_shape,
g_data, g_indices, g_indptr, g_shape) g_data, g_indices, g_indptr, g_shape)
return [g_data, DisconnectedType()(), DisconnectedType()(), DisconnectedType()()] return [g_data, DisconnectedType()(), DisconnectedType()(), DisconnectedType()()]
def infer_shape(self, node, shapes): def infer_shape(self, node, shapes):
if self.kmap is None:
# node.inputs[3] is of lenght as we only support sparse matrix. # node.inputs[3] is of lenght as we only support sparse matrix.
return [(node.inputs[3][0], node.inputs[3][1])] return [(node.inputs[3][0], node.inputs[3][1])]
else:
raise theano.tensor.basic.ShapeError("case not implemented")
CSC = CSM('csc') CSC = CSM('csc')
""" """
...@@ -844,25 +785,16 @@ class CSMGrad(gof.op.Op): ...@@ -844,25 +785,16 @@ class CSMGrad(gof.op.Op):
# 2. The elements in the sparse dimension are not guaranteed to be sorted. # 2. The elements in the sparse dimension are not guaranteed to be sorted.
# Therefore, the input data vector may have a different order than the # Therefore, the input data vector may have a different order than the
# gradient data vector. # gradient data vector.
__props__ = ()
def __init__(self, kmap=None): def __init__(self, kmap=None):
self.kmap = kmap if kmap is not None:
raise Exception("Do not use kmap, it is removed")
# This class always allocate a new output. # This class always allocate a new output.
# I keep this here to help GD understand what this kmap think is. # I keep this here to help GD understand what this kmap think is.
# if self.kmap is None: # if self.kmap is None:
# self.view_map = {0: [1]} # self.view_map = {0: [1]}
def __eq__(self, other):
return type(self) == type(other) and _kmap_eq(self.kmap, other.kmap)
def __hash__(self):
return 82345 ^ hash(type(self)) ^ _kmap_hash(self.kmap)
def __str__(self):
return "%s{%s}" % (
self.__class__.__name__,
self.kmap)
def make_node(self, x_data, x_indices, x_indptr, x_shape, def make_node(self, x_data, x_indices, x_indptr, x_shape,
g_data, g_indices, g_indptr, g_shape): g_data, g_indices, g_indptr, g_shape):
gout_data = g_data.type() gout_data = g_data.type()
...@@ -891,18 +823,11 @@ class CSMGrad(gof.op.Op): ...@@ -891,18 +823,11 @@ class CSMGrad(gof.op.Op):
for j_ptr in range(g_indptr[i], g_indptr[i + 1]): for j_ptr in range(g_indptr[i], g_indptr[i + 1]):
g_row[g_indices[j_ptr]] = 0 g_row[g_indices[j_ptr]] = 0
if self.kmap is None:
g_out[0] = gout_data g_out[0] = gout_data
else:
grad = numpy.zeros_like(x_data)
grad[self.kmap] = gout_data
g_out[0] = grad
def infer_shape(self, node, shapes): def infer_shape(self, node, shapes):
if self.kmap is None:
return [shapes[1]] return [shapes[1]]
else:
return [shapes[0]]
csm_grad = CSMGrad csm_grad = CSMGrad
......
...@@ -43,12 +43,6 @@ class ConvolutionIndices(Op): ...@@ -43,12 +43,6 @@ class ConvolutionIndices(Op):
""" """
__props__ = () __props__ = ()
@staticmethod
def sparse_eval(inshp, kshp, nkern, strides=(1, 1), mode='valid'):
(dx, dy) = strides
return convolution_indices.evaluate(inshp, kshp, (dx, dy),
nkern, mode=mode, ws=False)
@staticmethod @staticmethod
def conv_eval(inshp, kshp, strides=(1, 1), mode='valid'): def conv_eval(inshp, kshp, strides=(1, 1), mode='valid'):
(dx, dy) = strides (dx, dy) = strides
...@@ -73,7 +67,7 @@ class ConvolutionIndices(Op): ...@@ -73,7 +67,7 @@ class ConvolutionIndices(Op):
:param mode: 'valid' generates output only when kernel and :param mode: 'valid' generates output only when kernel and
image overlap overlap fully. Convolution obtained image overlap overlap fully. Convolution obtained
by zero-padding the input by zero-padding the input
:param ws: True if weight sharing, false otherwise :param ws: must be always True
:param (dx,dy): offset parameter. In the case of no weight sharing, :param (dx,dy): offset parameter. In the case of no weight sharing,
gives the pixel offset between two receptive fields. gives the pixel offset between two receptive fields.
With weight sharing gives the offset between the With weight sharing gives the offset between the
...@@ -83,6 +77,9 @@ class ConvolutionIndices(Op): ...@@ -83,6 +77,9 @@ class ConvolutionIndices(Op):
:returns: the structure of a sparse matrix, and the logical dimensions :returns: the structure of a sparse matrix, and the logical dimensions
of the image which will be the result of filtering. of the image which will be the result of filtering.
""" """
if not ws:
raise Exception("ws is obsolete and it must be always True")
(dx, dy) = strides (dx, dy) = strides
N = numpy N = numpy
...@@ -267,75 +264,6 @@ class ConvolutionIndices(Op): ...@@ -267,75 +264,6 @@ class ConvolutionIndices(Op):
convolution_indices = ConvolutionIndices() convolution_indices = ConvolutionIndices()
def applySparseFilter(kerns, kshp, nkern, images, imgshp,
step=(1, 1), bias=None, mode='valid'):
"""
"images" is assumed to be a matrix of shape batch_size x img_size,
where the second dimension represents each image in raster order
Output feature map will have shape:
.. code-block:: python
batch_size x number of kernels * output_size
.. note::
IMPORTANT: note that this means that each feature map is
contiguous in memory.
The memory layout will therefore be:
[ <feature_map_0> <feature_map_1> ... <feature_map_n>],
where <feature_map> represents a "feature map" in raster order
Note that the concept of feature map doesn't really apply to
sparse filters without weight sharing. Basically, nkern=1 will
generate one output img/feature map, nkern=2 a second feature map,
etc.
kerns is a 1D tensor, and assume to be of shape:
.. code-block:: python
nkern * N.prod(outshp) x N.prod(kshp)
Each filter is applied seperately to consecutive output pixels.
:param kerns: nkern*outsize*ksize vector containing kernels
:param kshp: tuple containing actual dimensions of kernel (not symbolic)
:param nkern: number of kernels to apply at each pixel in the
input image. nkern=1 will apply a single unique
filter for each input pixel.
:param images: bsize x imgsize matrix containing images on which
to apply filters
:param imgshp: tuple containing actual image dimensions (not symbolic)
:param step: determines number of pixels between adjacent receptive fields
(tuple containing dx,dy values)
:param mode: 'full', 'valid' see CSM.evaluate function for details
:return: out1, symbolic result
:return: out2, logical shape of the output img (nkern,height,width)
(after dot product, not of the sparse matrix!)
"""
# inshp contains either 2 entries (height,width) or 3 (nfeatures,h,w)
# in the first case, default nfeatures to 1
if numpy.size(imgshp) == 2:
imgshp = (1,) + imgshp
# construct indices and index pointers for sparse matrix
indices, indptr, spmat_shape, sptype, outshp, kmap = \
convolution_indices.sparse_eval(imgshp, kshp, nkern, step, mode)
# build a sparse weight matrix
sparsew = theano.sparse.CSM(sptype, kmap)(kerns, indices,
indptr, spmat_shape)
output = sparse.structured_dot(sparsew, images.T).T
if bias is not None:
output += bias
return output, numpy.hstack((nkern, outshp))
def convolve(kerns, kshp, nkern, images, imgshp, step=(1, 1), bias=None, def convolve(kerns, kshp, nkern, images, imgshp, step=(1, 1), bias=None,
mode='valid', flatten=True): mode='valid', flatten=True):
"""Convolution implementation by sparse matrix multiplication. """Convolution implementation by sparse matrix multiplication.
......
...@@ -130,133 +130,6 @@ class TestSP(unittest.TestCase): ...@@ -130,133 +130,6 @@ class TestSP(unittest.TestCase):
# profmode.print_summary() # profmode.print_summary()
@attr('slow')
def test_sparse(self):
# print '\n\n*************************************************'
# print ' TEST SPARSE'
# print '*************************************************'
# fixed parameters
bsize = 10 # batch size
imshp = (8, 8)
kshp = (5, 5)
nkern = 1 # per output pixel
ssizes = ((1, 1), (2, 2))
convmodes = ('full', 'valid',)
# symbolic stuff
bias = tensor.dvector()
kerns = tensor.dvector()
input = tensor.dmatrix()
rng = numpy.random.RandomState(3423489)
import theano.gof as gof
for mode in (None,):
ntot, ttot = 0, 0
for conv_mode in convmodes:
for ss in ssizes:
output, outshp = sp.applySparseFilter(kerns, kshp,\
nkern, input, imshp, ss, bias=bias, mode=conv_mode)
f = function([kerns, bias, input], output, mode=mode)
# build actual input images
img2d = numpy.arange(bsize*numpy.prod(imshp)).reshape((bsize,)+imshp)
img1d = img2d.reshape(bsize, -1)
zeropad_img = numpy.zeros((bsize,\
img2d.shape[1]+2*(kshp[0]-1),\
img2d.shape[2]+2*(kshp[1]-1)))
zeropad_img[:, kshp[0]-1:kshp[0]-1+img2d.shape[1],
kshp[1]-1:kshp[1]-1+img2d.shape[2]] = img2d
# build kernel matrix -- flatten it for theano stuff
filters = numpy.arange(numpy.prod(outshp)*numpy.prod(kshp)).\
reshape(nkern, numpy.prod(outshp[1:]), numpy.prod(kshp))
spfilt = filters.flatten()
biasvals = numpy.arange(numpy.prod(outshp))
# compute output by hand
ntime1 = time.time()
refout = numpy.zeros((bsize, nkern, outshp[1], outshp[2]))
patch = numpy.zeros((kshp[0], kshp[1]))
for b in xrange(bsize):
for k in xrange(nkern):
pixi = 0 # pixel index in raster order
for j in xrange(outshp[1]):
for i in xrange(outshp[2]):
n = j * ss[0]
m = i * ss[1]
patch = zeropad_img[b, n:n+kshp[0], m:m+kshp[1]]
refout[b, k, j, i] = numpy.dot(filters[k, pixi, :],\
patch.flatten())
pixi += 1
refout = refout.reshape(bsize, -1) + biasvals
ntot += time.time() - ntime1
# need to flatten images
ttime1 = time.time()
out1 = f(spfilt, biasvals, img1d)
ttot += time.time() - ttime1
temp = refout - out1
assert (temp < 1e-10).all()
# test downward propagation
vis = tensor.grad(0.5*tensor.sqr(output).sum(), input)
downprop = function([kerns, output], vis)
temp1 = time.time()
for zz in range(100):
visval = downprop(spfilt, out1)
indices, indptr, spmat_shape, sptype, outshp, kmap = \
sp.convolution_indices.sparse_eval(imshp, kshp, nkern, ss, conv_mode)
spmat = sparse.csc_matrix((spfilt[kmap], indices, indptr), spmat_shape)
visref = numpy.dot(out1, spmat.todense())
assert numpy.all(visref == visval), (visref, visval)
# print '**** Sparse Profiling Results (',mode,') ****'
# print 'Numpy processing time: ', ntot
# print 'Theano processing time: ', ttot
# profmode.print_summary()
@attr('slow')
def test_multilayer_sparse(self):
# fixed parameters
bsize = 10 # batch size
imshp = (5, 5)
kshp = ((3, 3), (2, 2))
nkerns = (10, 20) # per output pixel
ssizes = ((1, 1), (2, 2))
convmodes = ('full', 'valid',)
# symbolic stuff
kerns = [tensor.dvector(), tensor.dvector()]
input = tensor.dmatrix()
rng = numpy.random.RandomState(3423489)
# build actual input images
img2d = numpy.arange(bsize*numpy.prod(imshp)).reshape((bsize,)+imshp)
img1d = img2d.reshape(bsize, -1)
for mode in ('FAST_COMPILE', 'FAST_RUN'):
for conv_mode in convmodes:
for ss in ssizes:
l1hid, l1outshp = sp.applySparseFilter(kerns[0], kshp[0],\
nkerns[0], input, imshp, ss, mode=conv_mode)
l2hid, l2outshp = sp.applySparseFilter(kerns[1], kshp[1],\
nkerns[1], l1hid, l1outshp, ss, mode=conv_mode)
l1propup = function([kerns[0], input], l1hid, mode=mode)
l2propup = function([kerns[1], l1hid], l2hid, mode=mode)
# actual values
l1kernvals = numpy.arange(numpy.prod(l1outshp)*numpy.prod(kshp[0]))
l2kernvals = numpy.arange(numpy.prod(l2outshp)*numpy.prod(kshp[1])*nkerns[0])
l1hidval = l1propup(l1kernvals, img1d)
l2hidval = l2propup(l2kernvals, l1hidval)
# this doesn't compare the output of anything... but I manually verified that the patches # this doesn't compare the output of anything... but I manually verified that the patches
# are properly generated # are properly generated
def test_multilayer_conv(self): def test_multilayer_conv(self):
...@@ -335,35 +208,6 @@ class TestSP(unittest.TestCase): ...@@ -335,35 +208,6 @@ class TestSP(unittest.TestCase):
return output return output
utt.verify_grad(mp, [imval.reshape(imval.shape[0], -1)]) utt.verify_grad(mp, [imval.reshape(imval.shape[0], -1)])
def test_CSMGrad(self):
imshp = (3, 3)
nkern = 1 # per output pixel
kshp = (2, 2)
#ssizes = ((1,1),(2,2))
ssizes = ((1, 1),)
#convmodes = ('full','valid',)
convmodes = ('full',)
kerns = tensor.dvector()
indices = tensor.ivector()
indptr = tensor.ivector()
spmat_shape = tensor.ivector()
for mode in ['FAST_COMPILE', 'FAST_RUN']:
for conv_mode in convmodes:
for ss in ssizes:
indvals, indptrvals, spshapevals, sptype, outshp, kmap = \
sp.convolution_indices.sparse_eval(imshp, kshp, nkern, ss, conv_mode)
kvals = numpy.random.random(nkern*numpy.prod(kshp)*numpy.prod(outshp)).flatten()
def d(kerns):
return theano.sparse.dense_from_sparse(
theano.sparse.CSM(sptype, kmap)(
kerns, indvals, indptrvals, spshapevals))
# symbolic stuff
utt.verify_grad(d, [kvals])
if __name__ == '__main__': if __name__ == '__main__':
if 0: if 0:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论