提交 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):
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
def as_sparse_variable(x, name=None):
"""
......@@ -517,9 +503,9 @@ class CSMProperties(gof.Op):
# we don't return a view of the shape, we create a new ndarray from the
# shape tuple.
__props__ = ()
view_map = {0: [0], 1: [0], 2: [0]}
kmap = None
"""
Indexing to speficied what part of the data parameter
should be use to construct the sparse matrix.
......@@ -527,18 +513,8 @@ class CSMProperties(gof.Op):
"""
def __init__(self, kmap=None):
self.kmap = kmap
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)
if kmap is not None:
raise Exception("Do not use kmap, it is removed")
def make_node(self, csm):
csm = as_sparse_variable(csm)
......@@ -551,14 +527,10 @@ class CSMProperties(gof.Op):
def perform(self, node, inputs, out):
(csm,) = inputs
if self.kmap is None:
out[0][0] = csm.data
else:
out[0][0] = csm.data[self.kmap]
if str(csm.data.dtype) == 'int32':
out[0][0] = theano._asarray(out[0][0], dtype='int32')
# 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[2][0] = theano._asarray(csm.indptr, dtype='int32')
out[3][0] = theano._asarray(csm.shape, dtype='int32')
......@@ -638,14 +610,12 @@ def csm_shape(csm):
class CSM(gof.Op):
# See doc in instance of this Op or function after this class definition.
kmap = None
"""
Indexing to speficied what part of the data parameter
should be used to construct the sparse matrix.
"""
_hashval = None
__props__ = ('format',)
"""
Pre-computed hash value, defined by __init__.
......@@ -655,33 +625,12 @@ class CSM(gof.Op):
if format not in ('csr', 'csc'):
raise ValueError("format must be one of: 'csr', 'csc'", format)
self.format = format
# for efficiency, if remap does nothing, then do not apply it
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):
if kmap is not None:
raise Exception("Do not use kmap, it is removed")
# should view the other inputs too, but viewing multiple
# inputs is not currently supported by the destroyhandler
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):
data = tensor.as_tensor_variable(data)
......@@ -726,18 +675,14 @@ class CSM(gof.Op):
# for efficiency, if remap does nothing, then do not apply it
(data, indices, indptr, shape) = inputs
(out,) = outputs
if self.kmap is not None:
data = data[self.kmap]
if len(shape) != 2:
raise ValueError('Shape should be an array of length 2')
if (data.shape != indices.shape and numpy.size(data) !=
numpy.size(self.kmap)):
if data.shape != indices.shape:
errmsg = ('Data (shape ' + repr(data.shape) +
' must have the same number of elements ' +
'as indices (shape' + repr(indices.shape) +
') or elements as kmap (' +
repr(numpy.size(self.kmap)) + ')')
')')
raise ValueError(errmsg)
if self.format == 'csc':
out[0] = scipy.sparse.csc_matrix((data, indices.copy(),
......@@ -757,17 +702,13 @@ class CSM(gof.Op):
(g_out,) = gout
g_data, g_indices, g_indptr, g_shape = csm_properties(g_out)
# 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)
return [g_data, DisconnectedType()(), DisconnectedType()(), DisconnectedType()()]
def infer_shape(self, node, shapes):
if self.kmap is None:
# node.inputs[3] is of lenght as we only support sparse matrix.
return [(node.inputs[3][0], node.inputs[3][1])]
else:
raise theano.tensor.basic.ShapeError("case not implemented")
CSC = CSM('csc')
"""
......@@ -844,25 +785,16 @@ class CSMGrad(gof.op.Op):
# 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
# gradient data vector.
__props__ = ()
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.
# I keep this here to help GD understand what this kmap think is.
# if self.kmap is None:
# 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,
g_data, g_indices, g_indptr, g_shape):
gout_data = g_data.type()
......@@ -891,18 +823,11 @@ class CSMGrad(gof.op.Op):
for j_ptr in range(g_indptr[i], g_indptr[i + 1]):
g_row[g_indices[j_ptr]] = 0
if self.kmap is None:
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):
if self.kmap is None:
return [shapes[1]]
else:
return [shapes[0]]
csm_grad = CSMGrad
......
......@@ -43,12 +43,6 @@ class ConvolutionIndices(Op):
"""
__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
def conv_eval(inshp, kshp, strides=(1, 1), mode='valid'):
(dx, dy) = strides
......@@ -73,7 +67,7 @@ class ConvolutionIndices(Op):
:param mode: 'valid' generates output only when kernel and
image overlap overlap fully. Convolution obtained
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,
gives the pixel offset between two receptive fields.
With weight sharing gives the offset between the
......@@ -83,6 +77,9 @@ class ConvolutionIndices(Op):
:returns: the structure of a sparse matrix, and the logical dimensions
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
N = numpy
......@@ -267,75 +264,6 @@ class ConvolutionIndices(Op):
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,
mode='valid', flatten=True):
"""Convolution implementation by sparse matrix multiplication.
......
......@@ -130,133 +130,6 @@ class TestSP(unittest.TestCase):
# 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
# are properly generated
def test_multilayer_conv(self):
......@@ -335,35 +208,6 @@ class TestSP(unittest.TestCase):
return output
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 0:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论