From 6a29883feb4cfb64090dbc58c517a01899dbfe13 Mon Sep 17 00:00:00 2001 From: kalyc Date: Fri, 7 Sep 2018 14:22:03 -0700 Subject: [PATCH 1/3] Add sparse concat operator support --- keras/backend/mxnet_backend.py | 14 ++++++++++-- tests/keras/backend/mxnet_sparse_test.py | 29 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/keras/backend/mxnet_backend.py b/keras/backend/mxnet_backend.py index ba766b76b1d..23813913203 100644 --- a/keras/backend/mxnet_backend.py +++ b/keras/backend/mxnet_backend.py @@ -17,6 +17,8 @@ _REENTRY = False NAME_SCOPE_STACK = [] +py_all = all + class name_scope(object): def __init__(self, name): @@ -2030,6 +2032,10 @@ def concatenate(tensors, axis=-1): # Returns A tensor. + + Note: + - MXNet supports sparse concat only for dim=0 + - https://mxnet.apache.org/api/python/symbol/sparse.html#mxnet.symbol.sparse.concat """ if axis < 0: rank = ndim(tensors[0]) @@ -2038,8 +2044,12 @@ def concatenate(tensors, axis=-1): else: axis = 0 - tensors = [t.symbol for t in tensors] - return KerasSymbol(mx.sym.concat(*tensors, dim=axis)) + symbols = [t.symbol for t in tensors] + + if py_all([is_sparse(t) for t in tensors]): + return KerasSymbol(mx.sym.sparse.concat(*symbols, dim=0)) + + return KerasSymbol(mx.sym.concat(*symbols, dim=axis)) @keras_mxnet_symbol diff --git a/tests/keras/backend/mxnet_sparse_test.py b/tests/keras/backend/mxnet_sparse_test.py index 0a66780dd17..f540c827d63 100644 --- a/tests/keras/backend/mxnet_sparse_test.py +++ b/tests/keras/backend/mxnet_sparse_test.py @@ -104,6 +104,35 @@ def test_sparse_dot(self): assert k_s.shape == k_d.shape assert_allclose(k_s, k_d, atol=1e-05) + def test_sparse_concat(self): + x_d = np.array([0, 7, 2, 3], dtype=np.float32) + x_r = np.array([0, 2, 2, 3], dtype=np.int64) + x_c = np.array([4, 3, 2, 3], dtype=np.int64) + + x_sparse_1 = sparse.csr_matrix((x_d, (x_r, x_c)), shape=(4, 5)) + + x_d = np.array([0, 7, 2, 3], dtype=np.float32) + x_r = np.array([0, 2, 2, 3], dtype=np.int64) + x_c = np.array([4, 3, 2, 3], dtype=np.int64) + + x_sparse_2 = sparse.csr_matrix((x_d, (x_r, x_c)), shape=(4, 5)) + + assert K.is_sparse(K.variable(x_sparse_1)) + assert K.is_sparse(K.variable(x_sparse_2)) + x_dense_1 = x_sparse_1.toarray() + x_dense_2 = x_sparse_2.toarray() + + k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_sparse_2)]) + assert K.is_sparse(k_s) + + k_s_d = K.eval(k_s) + + # mx.sym.sparse.concat only supported for axis=0 + k_d = K.eval(K.concatenate(tensors=[K.variable(x_dense_1), K.variable(x_dense_2)], axis=0)) + + assert k_s_d.shape == k_d.shape + assert_allclose(k_s_d, k_d, atol=1e-05) + if __name__ == '__main__': pytest.main([__file__]) From d2fa40330ac90d2e3f7a712f73cfea70688fade8 Mon Sep 17 00:00:00 2001 From: kalyc Date: Fri, 7 Sep 2018 15:33:13 -0700 Subject: [PATCH 2/3] Add unit test for concatenating on sparse and dense tensor --- tests/keras/backend/mxnet_sparse_test.py | 51 +++++++++++++++++++----- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/tests/keras/backend/mxnet_sparse_test.py b/tests/keras/backend/mxnet_sparse_test.py index f540c827d63..86612f848b2 100644 --- a/tests/keras/backend/mxnet_sparse_test.py +++ b/tests/keras/backend/mxnet_sparse_test.py @@ -105,17 +105,8 @@ def test_sparse_dot(self): assert_allclose(k_s, k_d, atol=1e-05) def test_sparse_concat(self): - x_d = np.array([0, 7, 2, 3], dtype=np.float32) - x_r = np.array([0, 2, 2, 3], dtype=np.int64) - x_c = np.array([4, 3, 2, 3], dtype=np.int64) - - x_sparse_1 = sparse.csr_matrix((x_d, (x_r, x_c)), shape=(4, 5)) - - x_d = np.array([0, 7, 2, 3], dtype=np.float32) - x_r = np.array([0, 2, 2, 3], dtype=np.int64) - x_c = np.array([4, 3, 2, 3], dtype=np.int64) - - x_sparse_2 = sparse.csr_matrix((x_d, (x_r, x_c)), shape=(4, 5)) + x_sparse_1 = self.generate_test_sparse_matrix() + x_sparse_2 = self.generate_test_sparse_matrix() assert K.is_sparse(K.variable(x_sparse_1)) assert K.is_sparse(K.variable(x_sparse_2)) @@ -133,6 +124,44 @@ def test_sparse_concat(self): assert k_s_d.shape == k_d.shape assert_allclose(k_s_d, k_d, atol=1e-05) + def test_sparse_concat_partial_dense(self): + x_sparse_1 = self.generate_test_sparse_matrix() + x_sparse_2 = self.generate_test_sparse_matrix() + + assert K.is_sparse(K.variable(x_sparse_1)) + x_dense_1 = x_sparse_1.toarray() + x_dense_2 = x_sparse_2.toarray() + + k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_dense_2)], axis=0) + assert not(K.is_sparse(k_s)) + + k_s_d = K.eval(k_s) + + # mx.sym.sparse.concat only supported for axis=0 + k_d = K.eval(K.concatenate(tensors=[K.variable(x_dense_1), K.variable(x_dense_2)], axis=0)) + + assert k_s_d.shape == k_d.shape + assert_allclose(k_s_d, k_d, atol=1e-05) + + def test_sparse_concat_axis_non_zero(self): + x_sparse_1 = self.generate_test_sparse_matrix() + x_sparse_2 = self.generate_test_sparse_matrix() + + assert K.is_sparse(K.variable(x_sparse_1)) + x_dense_1 = x_sparse_1.toarray() + x_dense_2 = x_sparse_2.toarray() + + k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_dense_2)], axis=0) + assert not (K.is_sparse(k_s)) + + k_s_d = K.eval(k_s) + + # mx.sym.sparse.concat only supported for axis=0 + k_d = K.eval(K.concatenate(tensors=[K.variable(x_dense_1), K.variable(x_dense_2)], axis=0)) + + assert k_s_d.shape == k_d.shape + assert_allclose(k_s_d, k_d, atol=1e-05) + if __name__ == '__main__': pytest.main([__file__]) From 7d704f4a30fef6d4e99484ee28a92594a59a27fa Mon Sep 17 00:00:00 2001 From: kalyc Date: Fri, 7 Sep 2018 17:13:01 -0700 Subject: [PATCH 3/3] Remove hard-coded axis value for sparse concat --- keras/backend/mxnet_backend.py | 4 ++-- tests/keras/backend/mxnet_sparse_test.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/keras/backend/mxnet_backend.py b/keras/backend/mxnet_backend.py index 23813913203..a6ec2a291c3 100644 --- a/keras/backend/mxnet_backend.py +++ b/keras/backend/mxnet_backend.py @@ -2046,8 +2046,8 @@ def concatenate(tensors, axis=-1): symbols = [t.symbol for t in tensors] - if py_all([is_sparse(t) for t in tensors]): - return KerasSymbol(mx.sym.sparse.concat(*symbols, dim=0)) + if axis == 0 and py_all([is_sparse(t) for t in tensors]): + return KerasSymbol(mx.sym.sparse.concat(*symbols, dim=axis)) return KerasSymbol(mx.sym.concat(*symbols, dim=axis)) diff --git a/tests/keras/backend/mxnet_sparse_test.py b/tests/keras/backend/mxnet_sparse_test.py index 86612f848b2..a54fc17160c 100644 --- a/tests/keras/backend/mxnet_sparse_test.py +++ b/tests/keras/backend/mxnet_sparse_test.py @@ -113,7 +113,7 @@ def test_sparse_concat(self): x_dense_1 = x_sparse_1.toarray() x_dense_2 = x_sparse_2.toarray() - k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_sparse_2)]) + k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_sparse_2)], axis=0) assert K.is_sparse(k_s) k_s_d = K.eval(k_s) @@ -151,13 +151,13 @@ def test_sparse_concat_axis_non_zero(self): x_dense_1 = x_sparse_1.toarray() x_dense_2 = x_sparse_2.toarray() - k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_dense_2)], axis=0) + k_s = K.concatenate(tensors=[K.variable(x_sparse_1), K.variable(x_dense_2)]) assert not (K.is_sparse(k_s)) k_s_d = K.eval(k_s) # mx.sym.sparse.concat only supported for axis=0 - k_d = K.eval(K.concatenate(tensors=[K.variable(x_dense_1), K.variable(x_dense_2)], axis=0)) + k_d = K.eval(K.concatenate(tensors=[K.variable(x_dense_1), K.variable(x_dense_2)])) assert k_s_d.shape == k_d.shape assert_allclose(k_s_d, k_d, atol=1e-05)