提交 0ae2dc70 authored 作者: Vivek Kulkarni's avatar Vivek Kulkarni

Added comments to the code to document understanding of Gradient of Subtensor patch

上级 f68f06ce
...@@ -21,9 +21,13 @@ import theano.tests.unittest_tools as utt ...@@ -21,9 +21,13 @@ import theano.tests.unittest_tools as utt
from theano.gradient import grad_not_implemented from theano.gradient import grad_not_implemented
from theano.sparse.type import SparseType, _is_sparse from theano.sparse.type import SparseType, _is_sparse
#Column compressed (CSC)
#Row compressed (CSR)
sparse_formats = ['csc', 'csr'] sparse_formats = ['csc', 'csr']
#Register an optimization that does a specialization
#Does the same thing but better
# TODO: move this decorator to the compile submodule # TODO: move this decorator to the compile submodule
def register_specialize(lopt, *tags, **kwargs): def register_specialize(lopt, *tags, **kwargs):
compile.optdb['specialize'].register((kwargs and kwargs.pop('name')) or compile.optdb['specialize'].register((kwargs and kwargs.pop('name')) or
...@@ -1710,33 +1714,55 @@ class AddSD(gof.op.Op): ...@@ -1710,33 +1714,55 @@ class AddSD(gof.op.Op):
:note: The grad implemented is structured on `x`. :note: The grad implemented is structured on `x`.
""" """
#Constructor of the object
def __init__(self, inplace=False, *args, **kwargs): def __init__(self, inplace=False, *args, **kwargs):
gof.Op.__init__(self, *args, **kwargs) gof.Op.__init__(self, *args, **kwargs)
self.inplace = inplace #Should we do inplace addition or not ?
self.inplace = inplace
if self.inplace: if self.inplace:
self.destroy_map = {0: [3]} #This is a hint to the local optimizer that says that the first
#output is the same as the 3rd input and no intermdiate storage
#needs to be allocated
self.destroy_map = {0: [3]}
def __eq__(self, other): def __eq__(self, other):
#Compare the inplace flag as well
return (type(self) == type(other)) and self.inplace == other.inplace return (type(self) == type(other)) and self.inplace == other.inplace
def __hash__(self): def __hash__(self):
#Now use the hash of inplace as well
return hash(type(self)) ^ hash(self.inplace) return hash(type(self)) ^ hash(self.inplace)
def __str__(self): def __str__(self):
#If we are running the inplace version, display that
# so that it is useful for debugging
if self.inplace: if self.inplace:
return self.__class__.__name__ + '{inplace}' return self.__class__.__name__ + '{inplace}'
return self.__class__.__name__ return self.__class__.__name__
# Op Contract implementation: make_node:
# Should return a Apply object that specifies what:
# 1. Input variables type are etc for the operation
# 2. What are the types of the output variables
# These should be Theano variables
def make_node(self, x, y): def make_node(self, x, y):
# x is a sparse matrix, y is a dense one
# Wrap them around theano variables as this must be symbolic
x, y = as_sparse_variable(x), tensor.as_tensor_variable(y) x, y = as_sparse_variable(x), tensor.as_tensor_variable(y)
# If the types of both variables are of different types
# this is bad as theres a type mismatch
if x.type.dtype != y.type.dtype: if x.type.dtype != y.type.dtype:
raise NotImplementedError() raise NotImplementedError()
# The magic number two here arises because L{scipy.sparse} #Obtains the indices, indpt, data of NNZ sparse matrix x
# objects must be matrices (have dimension 2)
indices, indptr, data = csm_indices(x), csm_indptr(x), csm_data(x) indices, indptr, data = csm_indices(x), csm_indptr(x), csm_data(x)
# We either use CSC or CSR depending on the format of input
self.format = x.format self.format = x.format
# The magic number two here arises because L{scipy.sparse}
# objects must be matrices (have dimension 2)
assert y.type.ndim == 2 assert y.type.ndim == 2
return gof.Apply(self, return gof.Apply(self,
[data, indices, indptr, y], [data, indices, indptr, y],
...@@ -1789,21 +1815,25 @@ class AddSD(gof.op.Op): ...@@ -1789,21 +1815,25 @@ class AddSD(gof.op.Op):
def perform(self, node, (data, indices, indptr, y), (out, )): def perform(self, node, (data, indices, indptr, y), (out, )):
assert _is_dense(y) assert _is_dense(y)
if self.inplace: if self.inplace: #inplace enabled
if self.format == 'csc': if self.format == 'csc': #column compressed
for c in xrange(y.shape[1]): for c in xrange(y.shape[1]): #Loop through each column
low = indptr[c] low = indptr[c] #indptr will pint to slice of indices array for column
high = indptr[c+1] high = indptr[c+1]
for ind in xrange(low, high): for ind in xrange(low, high):
y[(indices[ind], c)] += data[ind] y[(indices[ind], c)] += data[ind] #Add that data element
elif self.format == 'csr': elif self.format == 'csr':
#Case for row's. Symmetric to what was done for columns
for r in xrange(y.shape[0]): for r in xrange(y.shape[0]):
low = indptr[r] low = indptr[r]
high = indptr[r+1] high = indptr[r+1]
for ind in xrange(low, high): for ind in xrange(low, high):
y[(r, indices[ind])] += data[ind] y[(r, indices[ind])] += data[ind]
out[0] = y
out[0] = y #Output storage cell is y
else: else:
#If in place is not enabled, create back the sparse matrix and
# and just add them normally.
if self.format == 'csr': if self.format == 'csr':
x = scipy.sparse.csr_matrix( (data,indices,indptr), shape=y.shape) x = scipy.sparse.csr_matrix( (data,indices,indptr), shape=y.shape)
elif self.format == 'csc': elif self.format == 'csc':
...@@ -1818,6 +1848,7 @@ class AddSD(gof.op.Op): ...@@ -1818,6 +1848,7 @@ class AddSD(gof.op.Op):
return sp_ones_like(x) * gz, gz return sp_ones_like(x) * gz, gz
def infer_shape(self, node, shapes): def infer_shape(self, node, shapes):
#Shape of output is the shape of y
return [shapes[3]] return [shapes[3]]
add_s_d = AddSD() add_s_d = AddSD()
......
...@@ -36,11 +36,14 @@ def local_inplace_remove0(node): ...@@ -36,11 +36,14 @@ def local_inplace_remove0(node):
""" """
Optimization to insert inplace versions of Remove0. Optimization to insert inplace versions of Remove0.
""" """
# If inplace is not enabled, enable it and replace that op with a
# new op which has inplace enabled
if isinstance(node.op, sparse.Remove0) and not node.op.inplace: if isinstance(node.op, sparse.Remove0) and not node.op.inplace:
new_op = node.op.__class__(inplace=True) new_op = node.op.__class__(inplace=True)
new_node = new_op(*node.inputs) new_node = new_op(*node.inputs)
return [new_node] return [new_node]
return False return False
theano.compile.optdb.register('local_inplace_remove0', theano.compile.optdb.register('local_inplace_remove0',
gof.TopoOptimizer(local_inplace_remove0, gof.TopoOptimizer(local_inplace_remove0,
failure_callback=gof.TopoOptimizer.warn_inplace), failure_callback=gof.TopoOptimizer.warn_inplace),
......
...@@ -6479,6 +6479,7 @@ class AdvancedSubtensor1(Op): ...@@ -6479,6 +6479,7 @@ class AdvancedSubtensor1(Op):
assert len(inputs) == 2 assert len(inputs) == 2
# rval1 = [advanced_inc_subtensor1(zeros_like(inputs[0]), gz, inputs[1])] # rval1 = [advanced_inc_subtensor1(zeros_like(inputs[0]), gz, inputs[1])]
#Construct a sparse matrix to boost performance of gradient
rval1 = [ConstructSparse()(inputs[0], gz, inputs[1])] rval1 = [ConstructSparse()(inputs[0], gz, inputs[1])]
return rval1 + [DisconnectedType()()] * (len(inputs) - 1) return rval1 + [DisconnectedType()()] * (len(inputs) - 1)
...@@ -6493,7 +6494,7 @@ class AdvancedSubtensor1(Op): ...@@ -6493,7 +6494,7 @@ class AdvancedSubtensor1(Op):
return [ilist + x[1:]] return [ilist + x[1:]]
class ConstructSparse(Op): class ConstructSparse(Op):
"""Construct a sparse matrix out of a list of 2-D matrix rows""" """Construct a sparse CSC matrix out of a list of 2-D matrix rows"""
def __hash__(self): def __hash__(self):
return hash((type(self))) return hash((type(self)))
...@@ -6504,9 +6505,16 @@ class ConstructSparse(Op): ...@@ -6504,9 +6505,16 @@ class ConstructSparse(Op):
def __str__(self): def __str__(self):
return self.__class__.__name__ return self.__class__.__name__
# self: this object
# x: x is a dense matrix
# y: y is a dense matrix (small) which has data
# ilist is the list of rows to which we want to copy rows of y into x
# Output must be a sparse representation of x
def make_node(self, x, y, ilist): def make_node(self, x, y, ilist):
#Convert to a sparse matrix, the shape is what is needed
x_sparse = ssparse.csc_matrix(tuple(x.shape.eval()), dtype=x.dtype) x_sparse = ssparse.csc_matrix(tuple(x.shape.eval()), dtype=x.dtype)
# Wrap into a Theano variable
x__ = theano.sparse.as_sparse_variable(x_sparse) x__ = theano.sparse.as_sparse_variable(x_sparse)
x_ = as_tensor_variable(x) x_ = as_tensor_variable(x)
...@@ -6528,30 +6536,44 @@ class ConstructSparse(Op): ...@@ -6528,30 +6536,44 @@ class ConstructSparse(Op):
' by y with ndim=%s to x subtensor with ndim=%s ' % ( ' by y with ndim=%s to x subtensor with ndim=%s ' % (
opname, x_.type.ndim, y_.type.ndim)) opname, x_.type.ndim, y_.type.ndim))
# Return the Apply instance
return Apply(self, [x_, y_, ilist_], [x__.type()]) return Apply(self, [x_, y_, ilist_], [x__.type()])
# Inp: Will contain x, values: that is y, and list of row indices of X,
# we want to copy the rows of y
def perform(self, node, inp, out_): def perform(self, node, inp, out_):
x, values, idx = inp x, values, idx = inp #Get all the 3 inputs
out, = out_ out, = out_ #get the output
rows, cols = values.shape rows, cols = values.shape #Get the shape of Y
assert rows == len(idx) assert rows == len(idx) #Each row is copied to a row in X.
# Setup the index pointer array
indptr = numpy.arange(cols+1) * rows indptr = numpy.arange(cols+1) * rows
#Set up the indices array
indices = as_strided(idx, indices = as_strided(idx,
strides=(0, idx.strides[0]), strides=(0, idx.strides[0]),
shape=(cols, idx.shape[0])).flatten() shape=(cols, idx.shape[0])).flatten()
#The data values we need to construct the sparse matrix from
data = values.T.flatten() data = values.T.flatten()
#Construct the sparse CSC matrix using data, indices and index pointer
out[0] = ssparse.csc_matrix((data,indices,indptr), shape=x.shape, dtype=x.dtype) out[0] = ssparse.csc_matrix((data,indices,indptr), shape=x.shape, dtype=x.dtype)
#Same as advancedIncSubTensor
def infer_shape(self, node, ishapes): def infer_shape(self, node, ishapes):
x, y, ilist = ishapes x, y, ilist = ishapes
return [x] return [x]
#Same as advancedIncSubTensor
def R_op(self, inputs, eval_points): def R_op(self, inputs, eval_points):
if None in eval_points[:2]: if None in eval_points[:2]:
return [None] return [None]
return self.make_node(eval_points[0], eval_points[1], return self.make_node(eval_points[0], eval_points[1],
*inputs[2:]).outputs *inputs[2:]).outputs
#Same as advancedIncSubTensor
def connection_pattern(self, node): def connection_pattern(self, node):
rval = [[True], [True]] rval = [[True], [True]]
...@@ -6561,6 +6583,7 @@ class ConstructSparse(Op): ...@@ -6561,6 +6583,7 @@ class ConstructSparse(Op):
return rval return rval
#Same as advancedIncSubTensor
def grad(self, inputs, grads): def grad(self, inputs, grads):
g_output, = grads g_output, = grads
x, y = inputs[:2] x, y = inputs[:2]
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论