Skip to content

Commit 4c56ffb

Browse files
authored
Merge pull request tensorflow#10056 from yongtang/10002-sparse_reduce_max
Add support for sparse_reduce_max and sparse_reduce_max_sparse
2 parents 4a7ceb4 + 0319d75 commit 4c56ffb

File tree

6 files changed

+257
-36
lines changed

6 files changed

+257
-36
lines changed

tensorflow/core/kernels/BUILD

+4-4
Original file line numberDiff line numberDiff line change
@@ -2459,7 +2459,7 @@ tf_cc_tests(
24592459
":ops_util",
24602460
":sparse_add_op",
24612461
":sparse_dense_binary_op_shared",
2462-
":sparse_reduce_sum_op",
2462+
":sparse_reduce_op",
24632463
"//tensorflow/core:core_cpu",
24642464
"//tensorflow/core:framework",
24652465
"//tensorflow/core:lib",
@@ -3202,7 +3202,7 @@ cc_library(
32023202
":sparse_cross_op",
32033203
":sparse_dense_binary_op_shared",
32043204
":sparse_fill_empty_rows_op",
3205-
":sparse_reduce_sum_op",
3205+
":sparse_reduce_op",
32063206
":sparse_reorder_op",
32073207
":sparse_reshape_op",
32083208
":sparse_softmax",
@@ -3258,8 +3258,8 @@ tf_kernel_library(
32583258
)
32593259

32603260
tf_kernel_library(
3261-
name = "sparse_reduce_sum_op",
3262-
prefix = "sparse_reduce_sum_op",
3261+
name = "sparse_reduce_op",
3262+
prefix = "sparse_reduce_op",
32633263
deps = SPARSE_DEPS,
32643264
)
32653265

tensorflow/core/kernels/sparse_reduce_sum_op.cc tensorflow/core/kernels/sparse_reduce_op.cc

+56-20
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,30 @@ Status ValidateInputs(const Tensor *shape_t, const Tensor *reduction_axes_t) {
130130
return Status::OK();
131131
}
132132

133-
template <typename T>
134-
class SparseReduceSumOp : public OpKernel {
133+
struct SumOp {
134+
template <typename T>
135+
static void Run(OpKernelContext *ctx, typename TTypes<T>::Scalar &s, const typename TTypes<T>::UnalignedVec &v) {
136+
s.device(ctx->eigen_cpu_device()) = v.sum();
137+
}
138+
static StringPiece Name() {
139+
return "sum";
140+
}
141+
};
142+
143+
struct MaxOp {
144+
template <typename T>
145+
static void Run(OpKernelContext *ctx, typename TTypes<T>::Scalar &s, const typename TTypes<T>::UnalignedVec &v) {
146+
s.device(ctx->eigen_cpu_device()) = v.maximum();
147+
}
148+
static StringPiece Name() {
149+
return "max";
150+
}
151+
};
152+
153+
template <typename T, typename Op>
154+
class SparseReduceOp : public OpKernel {
135155
public:
136-
explicit SparseReduceSumOp(OpKernelConstruction *ctx) : OpKernel(ctx) {
156+
explicit SparseReduceOp(OpKernelConstruction *ctx) : OpKernel(ctx) {
137157
OP_REQUIRES_OK(ctx, ctx->GetAttr("keep_dims", &keep_dims_));
138158
}
139159

@@ -163,10 +183,10 @@ class SparseReduceSumOp : public OpKernel {
163183
auto out_flat = out_values->flat<T>();
164184
out_flat.setZero();
165185

166-
Tensor tmp_group_sum;
186+
Tensor tmp_reduced_val;
167187
OP_REQUIRES_OK(ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
168-
TensorShape({}), &tmp_group_sum));
169-
auto group_sum = tmp_group_sum.scalar<T>();
188+
TensorShape({}), &tmp_reduced_val));
189+
auto reduced_val = tmp_reduced_val.scalar<T>();
170190

171191
// Compute strides, and use it to convert coords to flat index. The
172192
// coordinates returned by .group() have the same ndims as group_by_dims.
@@ -196,11 +216,12 @@ class SparseReduceSumOp : public OpKernel {
196216
// g.group() provides the coordinates of a particular reduced value.
197217
sp.Reorder<T>(reduction.reorder_dims);
198218
for (const auto &g : sp.group(reduction.group_by_dims)) {
199-
group_sum.device(ctx->eigen_cpu_device()) = g.template values<T>().sum();
219+
Op::template Run<T>(ctx, reduced_val, g.template values<T>());
200220
const int64 idx = CoordinatesToFlatIndex(g.group(), output_strides);
201-
out_flat(idx) = group_sum();
221+
out_flat(idx) = reduced_val();
202222
VLOG(2) << "coords: " << str_util::Join(g.group(), ",")
203-
<< "; idx: " << idx << "; group sum: " << group_sum();
223+
<< "; idx: " << idx << "; group " << Op::Name() << ": "
224+
<< reduced_val();
204225
}
205226
}
206227

@@ -212,14 +233,21 @@ class SparseReduceSumOp : public OpKernel {
212233
#define REGISTER_KERNELS(T) \
213234
REGISTER_KERNEL_BUILDER( \
214235
Name("SparseReduceSum").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
215-
SparseReduceSumOp<T>)
236+
SparseReduceOp<T, SumOp>)
216237
TF_CALL_NUMBER_TYPES(REGISTER_KERNELS);
217238
#undef REGISTER_KERNELS
218239

219-
template <typename T>
220-
class SparseReduceSumSparseOp : public OpKernel {
240+
#define REGISTER_KERNELS(T) \
241+
REGISTER_KERNEL_BUILDER( \
242+
Name("SparseReduceMax").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
243+
SparseReduceOp<T, MaxOp>)
244+
TF_CALL_REAL_NUMBER_TYPES(REGISTER_KERNELS);
245+
#undef REGISTER_KERNELS
246+
247+
template <typename T, typename Op>
248+
class SparseReduceSparseOp : public OpKernel {
221249
public:
222-
explicit SparseReduceSumSparseOp(OpKernelConstruction *ctx) : OpKernel(ctx) {
250+
explicit SparseReduceSparseOp(OpKernelConstruction *ctx) : OpKernel(ctx) {
223251
OP_REQUIRES_OK(ctx, ctx->GetAttr("keep_dims", &keep_dims_));
224252
}
225253

@@ -260,13 +288,13 @@ class SparseReduceSumSparseOp : public OpKernel {
260288
ctx->allocate_output(1, TensorShape({nnz}), &out_values_t));
261289
auto out_flat = out_values_t->flat<T>();
262290

263-
Tensor tmp_group_sum;
291+
Tensor tmp_reduced_val;
264292
OP_REQUIRES_OK(ctx, ctx->allocate_temp(DataTypeToEnum<T>::value,
265-
TensorShape({}), &tmp_group_sum));
266-
auto group_sum = tmp_group_sum.scalar<T>();
293+
TensorShape({}), &tmp_reduced_val));
294+
auto reduced_val = tmp_reduced_val.scalar<T>();
267295
int64 i = 0;
268296
for (const auto &g : sp.group(reduction.group_by_dims)) {
269-
group_sum.device(ctx->eigen_cpu_device()) = g.template values<T>().sum();
297+
Op::template Run<T>(ctx, reduced_val, g.template values<T>());
270298
std::vector<int64> group = g.group();
271299
for (int64 j = 0; j < group.size(); j++) {
272300
if (keep_dims_) {
@@ -275,10 +303,11 @@ class SparseReduceSumSparseOp : public OpKernel {
275303
out_indices_mat(i, j) = group[j];
276304
}
277305
}
278-
out_flat(i) = group_sum();
306+
out_flat(i) = reduced_val();
279307
i++;
280308
VLOG(2) << "coords: " << str_util::Join(g.group(), ",")
281-
<< "; group sum: " << group_sum();
309+
<< "; group " << Op::Name() << ": "
310+
<< reduced_val();
282311
}
283312

284313
Tensor *out_shape_t;
@@ -298,8 +327,15 @@ class SparseReduceSumSparseOp : public OpKernel {
298327
#define REGISTER_KERNELS(T) \
299328
REGISTER_KERNEL_BUILDER( \
300329
Name("SparseReduceSumSparse").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
301-
SparseReduceSumSparseOp<T>)
330+
SparseReduceSparseOp<T, SumOp>)
302331
TF_CALL_NUMBER_TYPES(REGISTER_KERNELS);
303332
#undef REGISTER_KERNELS
304333

334+
#define REGISTER_KERNELS(T) \
335+
REGISTER_KERNEL_BUILDER( \
336+
Name("SparseReduceMaxSparse").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
337+
SparseReduceSparseOp<T, MaxOp>)
338+
TF_CALL_REAL_NUMBER_TYPES(REGISTER_KERNELS);
339+
#undef REGISTER_KERNELS
340+
305341
} // namespace tensorflow

tensorflow/core/ops/sparse_ops.cc

+69
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,75 @@ a_shape: 1-D. The `shape` of the `SparseTensor`, with shape `[ndims]`.
710710
b: `ndims`-D Tensor. With shape `a_shape`.
711711
)doc");
712712

713+
REGISTER_OP("SparseReduceMax")
714+
.Input("input_indices: int64")
715+
.Input("input_values: T")
716+
.Input("input_shape: int64")
717+
.Input("reduction_axes: int32")
718+
.Attr("keep_dims: bool = False")
719+
.Output("output: T")
720+
.Attr("T: realnumbertype")
721+
.SetShapeFn(shape_inference::UnknownShape)
722+
.Doc(R"doc(
723+
Computes the max of elements across dimensions of a SparseTensor.
724+
725+
This Op takes a SparseTensor and is the sparse counterpart to
726+
`tf.reduce_max()`. In particular, this Op also returns a dense `Tensor`
727+
instead of a sparse one.
728+
729+
Reduces `sp_input` along the dimensions given in `reduction_axes`. Unless
730+
`keep_dims` is true, the rank of the tensor is reduced by 1 for each entry in
731+
`reduction_axes`. If `keep_dims` is true, the reduced dimensions are retained
732+
with length 1.
733+
734+
If `reduction_axes` has no entries, all dimensions are reduced, and a tensor
735+
with a single element is returned. Additionally, the axes can be negative,
736+
which are interpreted according to the indexing rules in Python.
737+
738+
input_indices: 2-D. `N x R` matrix with the indices of non-empty values in a
739+
SparseTensor, possibly not in canonical ordering.
740+
input_values: 1-D. `N` non-empty values corresponding to `input_indices`.
741+
input_shape: 1-D. Shape of the input SparseTensor.
742+
reduction_axes: 1-D. Length-`K` vector containing the reduction axes.
743+
keep_dims: If true, retain reduced dimensions with length 1.
744+
output: `R-K`-D. The reduced Tensor.
745+
)doc");
746+
747+
REGISTER_OP("SparseReduceMaxSparse")
748+
.Input("input_indices: int64")
749+
.Input("input_values: T")
750+
.Input("input_shape: int64")
751+
.Input("reduction_axes: int32")
752+
.Attr("keep_dims: bool = False")
753+
.Output("output_indices: int64")
754+
.Output("output_values: T")
755+
.Output("output_shape: int64")
756+
.Attr("T: realnumbertype")
757+
.SetShapeFn(shape_inference::UnknownShape)
758+
.Doc(R"doc(
759+
Computes the max of elements across dimensions of a SparseTensor.
760+
761+
This Op takes a SparseTensor and is the sparse counterpart to
762+
`tf.reduce_max()`. In contrast to SparseReduceMax, this Op returns a
763+
SparseTensor.
764+
765+
Reduces `sp_input` along the dimensions given in `reduction_axes`. Unless
766+
`keep_dims` is true, the rank of the tensor is reduced by 1 for each entry in
767+
`reduction_axes`. If `keep_dims` is true, the reduced dimensions are retained
768+
with length 1.
769+
770+
If `reduction_axes` has no entries, all dimensions are reduced, and a tensor
771+
with a single element is returned. Additionally, the axes can be negative,
772+
which are interpreted according to the indexing rules in Python.
773+
774+
input_indices: 2-D. `N x R` matrix with the indices of non-empty values in a
775+
SparseTensor, possibly not in canonical ordering.
776+
input_values: 1-D. `N` non-empty values corresponding to `input_indices`.
777+
input_shape: 1-D. Shape of the input SparseTensor.
778+
reduction_axes: 1-D. Length-`K` vector containing the reduction axes.
779+
keep_dims: If true, retain reduced dimensions with length 1.
780+
)doc");
781+
713782
REGISTER_OP("SparseReduceSum")
714783
.Input("input_indices: int64")
715784
.Input("input_values: T")

tensorflow/python/kernel_tests/sparse_ops_test.py

+34-12
Original file line numberDiff line numberDiff line change
@@ -545,21 +545,24 @@ def testNoEmptyRows(self):
545545
self.assertAllEqual(empty_row_indicator_out, np.zeros(2).astype(np.bool))
546546

547547

548-
class SparseReduceSumTest(test_util.TensorFlowTestCase):
548+
class SparseReduceTest(test_util.TensorFlowTestCase):
549549

550-
# [[1, ?, 1]
551-
# [?, 1, ?]]
550+
# [[1, ?, 2]
551+
# [?, 3, ?]]
552552
# where ? is implictly-zero.
553553
ind = np.array([[0, 0], [0, 2], [1, 1]]).astype(np.int64)
554554
vals = np.array([1, 1, 1]).astype(np.int32)
555555
dense_shape = np.array([2, 3]).astype(np.int64)
556556

557-
def _compare(self, sp_t, reduction_axes, ndims, keep_dims):
557+
def _compare(self, sp_t, reduction_axes, ndims, keep_dims, do_sum):
558558
densified = sparse_ops.sparse_tensor_to_dense(sp_t).eval()
559559

560560
np_ans = densified
561561
if reduction_axes is None:
562-
np_ans = np.sum(np_ans, keepdims=keep_dims)
562+
if do_sum:
563+
np_ans = np.sum(np_ans, keepdims=keep_dims)
564+
else:
565+
np_ans = np.max(np_ans, keepdims=keep_dims)
563566
else:
564567
if not isinstance(reduction_axes, list): # Single scalar.
565568
reduction_axes = [reduction_axes]
@@ -569,24 +572,39 @@ def _compare(self, sp_t, reduction_axes, ndims, keep_dims):
569572
# Loop below depends on sorted.
570573
reduction_axes.sort()
571574
for ra in reduction_axes.ravel()[::-1]:
572-
np_ans = np.sum(np_ans, axis=ra, keepdims=keep_dims)
575+
if do_sum:
576+
np_ans = np.sum(np_ans, axis=ra, keepdims=keep_dims)
577+
else:
578+
np_ans = np.max(np_ans, axis=ra, keepdims=keep_dims)
573579

574580
with self.test_session():
575-
tf_dense_ans = sparse_ops.sparse_reduce_sum(sp_t, reduction_axes,
576-
keep_dims)
581+
if do_sum:
582+
tf_dense_ans = sparse_ops.sparse_reduce_sum(sp_t, reduction_axes,
583+
keep_dims)
584+
else:
585+
tf_dense_ans = sparse_ops.sparse_reduce_max(sp_t, reduction_axes,
586+
keep_dims)
577587
out_dense = tf_dense_ans.eval()
578588

579-
tf_sparse_ans = sparse_ops.sparse_reduce_sum_sparse(sp_t, reduction_axes,
580-
keep_dims)
589+
if do_sum:
590+
tf_sparse_ans = sparse_ops.sparse_reduce_sum_sparse(sp_t,
591+
reduction_axes,
592+
keep_dims)
593+
else:
594+
tf_sparse_ans = sparse_ops.sparse_reduce_max_sparse(sp_t,
595+
reduction_axes,
596+
keep_dims)
581597
# Convert to dense for comparison purposes.
582598
out_sparse = sparse_ops.sparse_tensor_to_dense(tf_sparse_ans).eval()
583599

584600
self.assertAllClose(np_ans, out_dense)
585601
self.assertAllClose(np_ans, out_sparse)
586602

587603
def _compare_all(self, sp_t, reduction_axes, ndims):
588-
self._compare(sp_t, reduction_axes, ndims, False)
589-
self._compare(sp_t, reduction_axes, ndims, True)
604+
self._compare(sp_t, reduction_axes, ndims, False, False)
605+
self._compare(sp_t, reduction_axes, ndims, False, True)
606+
self._compare(sp_t, reduction_axes, ndims, True, False)
607+
self._compare(sp_t, reduction_axes, ndims, True, True)
590608

591609
@unittest.skipIf(np.__version__ == "1.13.0", "numpy 1.13 bug")
592610
def testSimpleAndRandomInputs(self):
@@ -623,6 +641,10 @@ def testInvalidAxes(self):
623641
sparse_ops.sparse_reduce_sum(sp_t, -3).eval()
624642
with self.assertRaisesOpError("Invalid reduction dimension 2"):
625643
sparse_ops.sparse_reduce_sum(sp_t, 2).eval()
644+
with self.assertRaisesOpError("Invalid reduction dimension -3"):
645+
sparse_ops.sparse_reduce_max(sp_t, -3).eval()
646+
with self.assertRaisesOpError("Invalid reduction dimension 2"):
647+
sparse_ops.sparse_reduce_max(sp_t, 2).eval()
626648

627649
@unittest.skipIf(np.__version__ == "1.13.0", "numpy 1.13 bug")
628650
def testGradient(self):

0 commit comments

Comments
 (0)