def sparse_put(sp_tensor, sp_updates, name="sparse_put"): """Changes a given SparseTensor according to the updates specified in a SparseTensor. Creates a new tensor where the values of the updates override the values in the original tensor. The input tensors must have the same ``dense_shape``. Args: sp_tensor: a ``SparseTensor`` we which to set some indices to given values sp_updates: a ``SparseTensor`` with the indices to be changed and the respective values name: name for this operation (optional) Returns: ``SparseTensor``: a ``SparseTensor`` with the updated values. """ with ops.name_scope(name): if sp_updates.dtype != sp_tensor.dtype: sp_updates = math_ops.cast(sp_updates, sp_tensor.dtype) # 1 concat indices and establish final tensor shape update_shape = sp_updates.values.get_shape() zero_updates = SparseTensor( sp_updates.indices, array_ops.zeros(update_shape, dtype=dtypes.float32), sp_updates.dense_shape) proto_result = sp_ops.sparse_add(sp_tensor, zero_updates) # shape of resulting values tensor proto_shape = array_ops.shape(proto_result.values) # 2 get mask for input tensor proto_ones = SparseTensor(proto_result.indices, array_ops.ones(proto_shape, dtypes.int8), proto_result.dense_shape) # mask_ones = math_ops.scalar_mul(-1, array_ops.ones(update_shape)) sp_mask = SparseTensor( sp_updates.indices, array_ops.constant(-1, dtypes.int8, update_shape), sp_updates.dense_shape) to_retain = sp_ops.sparse_add(proto_ones, sp_mask) to_retain = math_ops.not_equal(to_retain.values, 0) # get tensor with masked values tensor_masked = sp_ops.sparse_retain(proto_result, to_retain) # add values to entries previously set to 0 new_tensor = sp_ops.sparse_add(tensor_masked, sp_updates) return new_tensor
def to_sparse(tensor, name="to_sparse"): """ Returns a ``SparseTensor` for a`given dense ``Tensor``. Example: For a dense ``Tensor`` such as:: tensor = [[1,0], [2,3]] this returns an op that creates the following two ``SparseTensor``:: SparseTensor(indices = [[0,0],[1,0],[1,1]], values = [1,2,3], dense_shape = [2,2]) Args: tensor: a dense ``Tensor`` name: name for this operation (optional) Returns: ``SparseTensor``: a sparse tensor with sparse index and value tensors with the non-zero entries of the given input. """ with ops.name_scope(name): indices = array_ops.where(math_ops.not_equal(tensor, 0)) dense_shape = array_ops.shape(tensor, out_type=dtypes.int64) values = array_ops.gather_nd(tensor, indices) sp_tensor = SparseTensor(indices, values, dense_shape) return sp_tensor
def sparse_dropout(sp_tensor, keep_prob=0.2, seed=None, name="sparse_dropout"): """Performs a dropout on a ``SparseTensor``. With probability `keep_prob`, outputs the input element scaled up by `1 / keep_prob`, otherwise outputs `0`. The scaling is so that the expected sum is unchanged. Args: sp_tensor: a ``SparseTensor`` on which the dropout is performed. keep_prob: A scalar `Tensor` with the same type as x. The probability that each element is kept. seed: A Python integer. Used to create random seeds. (See `TensorFlow` documentation for ``tf.set_random_seed`` for behavior.) name: A name for this operation (optional). """ with ops.name_scope(name): dense_shape = sp_tensor.dense_shape drop_values = dropout(sp_tensor.values, keep_prob, seed=seed) not_zero = math_ops.not_equal(drop_values, 0) # new indices (after dropout) # not_zero_indices = array_ops.where(not_zero) # indices = array_ops.gather_nd(sp_indices.indices, not_zero_indices) values = array_ops.boolean_mask(drop_values, not_zero) indices = array_ops.boolean_mask(sp_tensor.indices, not_zero) new_tensor = SparseTensor(indices, values, dense_shape) return new_tensor
def dense_put(tensor, sp_updates, name="dense_put"): """ Changes a given dense ``Tensor`` according to the updates specified in a ``SparseTensor``. Creates a new ``Tensor`` where the values of the updates override the values in the original tensor. The tensor `shape` must be the same as the updates `dense_shape`. Args: tensor: a ``Tensor`` we want to change. sp_updates: a ``SparseTensor`` with the indices to be changed and the respective values. name: the name for this operation (optional). Returns: ``Tensor``: a ``Tensor`` with the updated values. """ with ops.name_scope(name): tensor = ops.convert_to_tensor(tensor) if sp_updates.dtype != tensor.dtype: sp_updates = math_ops.cast(sp_updates, tensor.dtype) markers = array_ops.ones(shape=array_ops.shape(sp_updates.values)) sparse_marker_tensor = SparseTensor(indices=sp_updates.indices, values=markers, dense_shape=sp_updates.dense_shape) dense_update_marker = sp_ops.sparse_tensor_to_dense( sparse_marker_tensor) dense_updates = sp_ops.sparse_tensor_to_dense(sp_updates) new_tensor = array_ops.where( math_ops.not_equal(dense_update_marker, 0), dense_updates, tensor) return new_tensor
def __init__(self, n_units, batch_size=None, dtype=dtypes.float32, name="sparse_input"): """ Args: n_units (int): the number of output units for this layer batch_size (int): batch_size for the input, helps to define the shape for this sparse layer dtype: the output type for the values in the ``SparseTensor`` that this layer outputs name: name for the layer """ shape = [batch_size, n_units] super().__init__(None, n_units, shape, dtype, name) with ops.name_scope(name): self.placeholder = array_ops.sparse_placeholder( dtype, self.shape, name) n_indices = array_ops.shape(self.placeholder.indices)[0] n_values = array_ops.shape(self.placeholder.values)[0] valid_values = check_ops.assert_equal( n_indices, n_values, message="Invalid number of values") with ops.control_dependencies([valid_values]): values = array_ops.identity(self.placeholder.values) self.tensor = SparseTensor(self.placeholder.indices, values, self.placeholder.dense_shape)
def sp_indices_from_sp_tensor(sp_values): """ Returns the a ``SparseTensor`` with the indices for the active values on a given ``SparseTensor`` . Use Case: To be used with ``embedding_lookup_sparse`` when we need two ``SparseTensor`` : one with the indices and one with the values. Args: sp_values: a ``SparseTensor`` for which we extract the active indices. Returns: ``SparseTensor``: a ``SparseTensor`` with the indices of the active elements of another ``SparseTensor`` . """ _, flat_indices = array_ops.unstack(sp_values.indices, num=2, axis=-1) return SparseTensor(sp_values.indices, flat_indices, sp_values.dense_shape)
def sparse_ones(indices, dense_shape, dtype=dtypes.float32): """ Creates a new ``SparseTensor`` where the values are 1 Args: indices: a 2-D ``Tensor`` with the indices for the resulting sparse tensor dense_shape: the ``SparseTensor`` dense shape dtype: the tensor type for the values Returns: ``SparseTensor``: a new SparseTensor with the values set to 1. """ indices = to_tensor_cast(indices, dtypes.int64) dense_shape = to_tensor_cast(dense_shape, dtypes.int64) indices_shape = complete_shape(indices) values = array_ops.ones([indices_shape[0]], dtype) return SparseTensor(indices, values, dense_shape)
def sparse_random_normal(dense_shape, density=0.1, mean=0.0, stddev=1, dtype=dtypes.float32, seed=None): """ Creates a NxM `SparseTensor` with `density` * NXM non-zero entries The values for the sparse tensor come from a random normal distribution. Args: dense_shape: a list of integers, a tuple of integers density: the proportion of non-zero entries, 1 means that all the entries have a value sampled from a random normal distribution. mean: normal distribution mean stddev: normal distribution standard deviation seed: the seed used for the random number generator seed: the seed used for the random number generator Returns: A `SparseTensor` with a given density with values sampled from the normal distribution """ num_noise = int(density * dense_shape[1]) if num_noise == 0: return empty_sparse_tensor(dense_shape) else: flat_indices = sample(range_max=dense_shape[1], num_sampled=num_noise, batch_size=dense_shape[0], unique=True, seed=seed) indices = batch_to_matrix_indices(flat_indices, dtype=dtypes.int64) value_shape = tensor_shape.as_shape([dense_shape[0] * num_noise]) values = random_ops.random_normal(shape=value_shape, mean=mean, stddev=stddev, dtype=dtype, seed=seed) # dense_shape = tensor_shape.as_shape(dense_shape) sp_tensor = SparseTensor(indices, values, dense_shape) sp_tensor = sparse_ops.sparse_reorder(sp_tensor) return sp_tensor
def empty_sparse_tensor(dense_shape, dtype=dtypes.float32, name="empty_sp_tensor"): """ Creates an empty SparseTensor. Note: ``shape = [10]`` ``empty = tf.sparse_tensor_to_dense(transform.empty_sparse_tensor(shape))`` is equivalent to: ``zero = tf.zeros(shape)`` meaning that: ``tf.reduce_all(tf.equal(zeros,empty)).eval()`` returns True Args: dense_shape: a 1-D tensor, python list, or numpy array with the output shape for the sparse tensor dtype: the dtype of the values for the empty SparseTensor name: a name for this operation (optional) Returns: ``SparseTensor``: an empty sparse tensor with a given shape """ with ops.name_scope(name): dense_shape = ops.convert_to_tensor(dense_shape, name="dense_shape", dtype=dtypes.int64) index_shape = dense_shape.get_shape().with_rank(1) empty_indices = array_ops.ones([0, index_shape[0]], dtype=dtypes.int64) empty_values = array_ops.ones([0], dtype=dtype) return SparseTensor(empty_indices, empty_values, dense_shape)
def sparse_random_mask(dense_shape, density=0.5, mask_values=[1], symmetrical=True, dtype=dtypes.float32, seed=None): """Uses values to create a sparse random mask according to a given density a density of 0 returns an empty sparse tensor Note: if symmetrical the mask has always the same number of mask_values per row which means that if ``density * dense_shape[1] < len(mask_values)``, the mask will be an empty ``SparseTensor``. It also means that if ``dense_shape[1] % len(mask_values) != 0`` and ``density = 1.0``, not all values will be corrupted because we can't fill every entry with a symmetrical mask. There are other ways to fill a dense tensor with random values though so a density of 1 defeats the purpose of this operation. if not symmetrical the number of mask_values will not be the same per row. If we need to fill 2 extra entries with values 2 masked values are picked at random to fill the excess. Example: if **not** symmetrical and ``shape = [1,10]]`` ``density = 0.5`` ``mask_values = [1,2,3]`` the result could be something like:: [[1. 1. 2. 3. 0. 0. 0. 2. 0. 0.]] Args: seed: int32 to te used as seed dtype: tensor tensor value type dense_shape: a 1-D tensor tensor with shape [2] density: desired density mask_values: the values to be used to generate the random mask Returns: A sparse random mask with a density of the original shape corrupted using the mask values """ # total number of corrupted indices num_values = len(mask_values) num_corrupted = int(density * dense_shape[1]) num_mask_values = num_corrupted // num_values * num_values if num_mask_values == 0: return empty_sparse_tensor(dense_shape) else: # num corrupted indices per value if not symmetrical: mask_values = random_ops.random_shuffle(mask_values, seed) extra_corrupted = num_corrupted - num_mask_values if not symmetrical: num_mask_values = num_corrupted samples = sample(dense_shape[1], num_mask_values, dense_shape[0], unique=True, seed=seed) indices = batch_to_matrix_indices(samples, dtype=dtypes.int64) value_tensors = [] for i in range(num_values): num_vi = num_mask_values // num_values # spread the extra to be corrupted by n mask_values if not symmetrical and i < extra_corrupted: num_vi = num_vi + 1 vi_shape = math_ops.cast([dense_shape[0], num_vi], dtypes.int32) vi_tensor = array_ops.fill(vi_shape, mask_values[i]) value_tensors.append(vi_tensor) values = array_ops.concat(value_tensors, axis=-1) values = array_ops.reshape(values, [-1]) if values.dtype != dtype: values = math_ops.cast(values, dtype) dense_shape = math_ops.cast([dense_shape[0], dense_shape[1]], dtypes.int64) sp_tensor = SparseTensor(indices, values, dense_shape) # the indices were generated at random so sp_tensor = sparse_ops.sparse_reorder(sp_tensor) return sp_tensor