diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index f055b568090..e1d34227305 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -2614,6 +2614,17 @@ py_library( ], ) +py_test( + name = "sparse_ops_test", + srcs = ["ops/sparse_ops_test.py"], + srcs_version = "PY2AND3", + deps = [ + ":constant_op", + ":framework_test_lib", + ":sparse_ops", + ], +) + py_library( name = "spectral_grad", srcs = ["ops/spectral_grad.py"], diff --git a/tensorflow/python/ops/sparse_ops.py b/tensorflow/python/ops/sparse_ops.py index e91813b4a87..d990386b9a1 100644 --- a/tensorflow/python/ops/sparse_ops.py +++ b/tensorflow/python/ops/sparse_ops.py @@ -41,6 +41,7 @@ from tensorflow.python.ops import math_ops # pylint: disable=wildcard-import from tensorflow.python.ops.gen_sparse_ops import * # pylint: enable=wildcard-import +from tensorflow.python.util import compat from tensorflow.python.util import deprecation from tensorflow.python.util.tf_export import tf_export @@ -85,6 +86,50 @@ def _convert_to_sparse_tensors(sp_inputs): raise TypeError("Inputs must be a list or tuple.") +def _make_int64_tensor(value, name): + if isinstance(value, compat.integral_types): + return ops.convert_to_tensor(value, name=name, dtype=dtypes.int64) + if not isinstance(value, ops.Tensor): + raise TypeError("{} must be an integer value".format(name)) + if value.dtype == dtypes.int64: + return value + return math_ops.cast(value, dtypes.int64) + + +@tf_export("sparse.eye") +def sparse_eye(num_rows, + num_columns=None, + dtype=dtypes.float32, + name=None): + """Creates a two-dimensional sparse tensor with ones along the diagonal. + + Args: + num_rows: Non-negative integer or `int32` scalar `tensor` giving the number + of rows in the resulting matrix. + num_columns: Optional non-negative integer or `int32` scalar `tensor` giving + the number of columns in the resulting matrix. Defaults to `num_rows`. + dtype: The type of element in the resulting `Tensor`. + name: A name for this `Op`. Defaults to "eye". + + Returns: + A `SparseTensor` of shape [num_rows, num_columns] with ones along the + diagonal. + """ + with ops.name_scope(name, default_name="eye", values=[num_rows, num_columns]): + num_rows = _make_int64_tensor(num_rows, "num_rows") + num_columns = num_rows if num_columns is None else _make_int64_tensor( + num_columns, "num_columns") + + # Create the sparse tensor. + diag_size = math_ops.minimum(num_rows, num_columns) + diag_range = math_ops.range(diag_size, dtype=dtypes.int64) + + return sparse_tensor.SparseTensor( + indices=array_ops.stack([diag_range, diag_range], axis=1), + values=array_ops.ones(diag_size, dtype=dtype), + dense_shape=[num_rows, num_columns]) + + # pylint: disable=protected-access @tf_export("sparse_concat") @deprecation.deprecated_args( diff --git a/tensorflow/python/ops/sparse_ops_test.py b/tensorflow/python/ops/sparse_ops_test.py new file mode 100644 index 00000000000..b10c3c21878 --- /dev/null +++ b/tensorflow/python/ops/sparse_ops_test.py @@ -0,0 +1,49 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for sparse ops.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +from tensorflow.python.framework import constant_op +from tensorflow.python.framework import test_util +from tensorflow.python.ops import sparse_ops +from tensorflow.python.platform import googletest + + +@test_util.run_all_in_graph_and_eager_modes +class SparseOpsTest(test_util.TensorFlowTestCase): + + def testSparseEye(self): + def test_one(n, m, as_tensors): + expected = np.eye(n, m) + if as_tensors: + m = constant_op.constant(m) + n = constant_op.constant(n) + s = sparse_ops.sparse_eye(n, m) + d = sparse_ops.sparse_to_dense(s.indices, s.dense_shape, s.values) + self.assertAllEqual(self.evaluate(d), expected) + + for n in range(2, 10, 2): + for m in range(2, 10, 2): + # Test with n and m as both constants and tensors. + test_one(n, m, True) + test_one(n, m, False) + +if __name__ == '__main__': + googletest.main() diff --git a/tensorflow/tools/api/golden/v1/tensorflow.sparse.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.sparse.pbtxt index bbfe395031a..3f54bc33e78 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.sparse.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.sparse.pbtxt @@ -8,4 +8,8 @@ tf_module { name: "cross_hashed" argspec: "args=[\'inputs\', \'num_buckets\', \'hash_key\', \'name\'], varargs=None, keywords=None, defaults=[\'0\', \'None\', \'None\'], " } + member_method { + name: "eye" + argspec: "args=[\'num_rows\', \'num_columns\', \'dtype\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \"\", \'None\'], " + } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.sparse.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.sparse.pbtxt index bbfe395031a..3f54bc33e78 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.sparse.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.sparse.pbtxt @@ -8,4 +8,8 @@ tf_module { name: "cross_hashed" argspec: "args=[\'inputs\', \'num_buckets\', \'hash_key\', \'name\'], varargs=None, keywords=None, defaults=[\'0\', \'None\', \'None\'], " } + member_method { + name: "eye" + argspec: "args=[\'num_rows\', \'num_columns\', \'dtype\', \'name\'], varargs=None, keywords=None, defaults=[\'None\', \"\", \'None\'], " + } }